@magentrix-corp/magentrix-cli 1.3.16 → 1.3.17

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 (68) hide show
  1. package/LICENSE +25 -25
  2. package/README.md +1166 -1166
  3. package/actions/autopublish.old.js +293 -293
  4. package/actions/config.js +182 -182
  5. package/actions/create.js +466 -466
  6. package/actions/help.js +164 -164
  7. package/actions/iris/buildStage.js +874 -874
  8. package/actions/iris/delete.js +256 -256
  9. package/actions/iris/dev.js +391 -391
  10. package/actions/iris/index.js +6 -6
  11. package/actions/iris/link.js +375 -375
  12. package/actions/iris/recover.js +268 -268
  13. package/actions/main.js +80 -80
  14. package/actions/publish.js +1420 -1420
  15. package/actions/pull.js +684 -684
  16. package/actions/setup.js +148 -148
  17. package/actions/status.js +17 -17
  18. package/actions/update.js +248 -248
  19. package/bin/magentrix.js +393 -393
  20. package/package.json +55 -55
  21. package/utils/assetPaths.js +158 -158
  22. package/utils/autopublishLock.js +77 -77
  23. package/utils/cacher.js +206 -206
  24. package/utils/cli/checkInstanceUrl.js +76 -74
  25. package/utils/cli/helpers/compare.js +282 -282
  26. package/utils/cli/helpers/ensureApiKey.js +63 -63
  27. package/utils/cli/helpers/ensureCredentials.js +68 -68
  28. package/utils/cli/helpers/ensureInstanceUrl.js +75 -75
  29. package/utils/cli/writeRecords.js +262 -262
  30. package/utils/compare.js +135 -135
  31. package/utils/compress.js +17 -17
  32. package/utils/config.js +527 -527
  33. package/utils/debug.js +144 -144
  34. package/utils/diagnostics/testPublishLogic.js +96 -96
  35. package/utils/diff.js +49 -49
  36. package/utils/downloadAssets.js +291 -291
  37. package/utils/filetag.js +115 -115
  38. package/utils/hash.js +14 -14
  39. package/utils/iris/backup.js +411 -411
  40. package/utils/iris/builder.js +541 -541
  41. package/utils/iris/config-reader.js +664 -664
  42. package/utils/iris/deleteHelper.js +150 -150
  43. package/utils/iris/errors.js +537 -537
  44. package/utils/iris/linker.js +601 -601
  45. package/utils/iris/lock.js +360 -360
  46. package/utils/iris/validation.js +360 -360
  47. package/utils/iris/validator.js +281 -281
  48. package/utils/iris/zipper.js +248 -248
  49. package/utils/logger.js +291 -291
  50. package/utils/magentrix/api/assets.js +220 -220
  51. package/utils/magentrix/api/auth.js +107 -107
  52. package/utils/magentrix/api/createEntity.js +61 -61
  53. package/utils/magentrix/api/deleteEntity.js +55 -55
  54. package/utils/magentrix/api/iris.js +251 -251
  55. package/utils/magentrix/api/meqlQuery.js +36 -36
  56. package/utils/magentrix/api/retrieveEntity.js +86 -86
  57. package/utils/magentrix/api/updateEntity.js +66 -66
  58. package/utils/magentrix/fetch.js +168 -168
  59. package/utils/merge.js +22 -22
  60. package/utils/permissionError.js +70 -70
  61. package/utils/preferences.js +40 -40
  62. package/utils/progress.js +469 -469
  63. package/utils/spinner.js +43 -43
  64. package/utils/template.js +52 -52
  65. package/utils/updateFileBase.js +121 -121
  66. package/utils/workspaces.js +108 -108
  67. package/vars/config.js +11 -11
  68. package/vars/global.js +50 -50
@@ -1,86 +1,86 @@
1
- import { fetchMagentrix } from "../fetch.js";
2
-
3
- /**
4
- * Lists all entities available from the Magentrix API.
5
- *
6
- * Makes an authenticated GET request to `/api/3.0/entity` to retrieve metadata
7
- * about all available entities (objects) in the Magentrix instance.
8
- *
9
- * Handles and throws both network errors and API-level errors with detailed messages.
10
- *
11
- * @async
12
- * @param {string} instanceUrl - The base URL of the Magentrix instance.
13
- * @param {string} token - The OAuth or access token for authentication.
14
- * @returns {Promise<Object>} Parsed JSON response with entities metadata.
15
- * @throws {Error} If required arguments are missing, network error occurs,
16
- * HTTP error is returned, or API-level errors are found in the response.
17
- */
18
- export const listEntities = async (instanceUrl, token) => {
19
- // --- Validate required input parameters ---
20
- if (!instanceUrl || !token) {
21
- throw new Error('Missing required Magentrix instanceUrl or token');
22
- }
23
-
24
- const data = await fetchMagentrix({
25
- instanceUrl,
26
- token,
27
- path: '/api/3.0/entity',
28
- method: "GET"
29
- })
30
-
31
- return data;
32
- };
33
-
34
- /**
35
- * Retrieves a specific ActiveClass or ActivePage entity by ID from Magentrix via the REST API.
36
- *
37
- * @async
38
- * @function retrieveEntity
39
- * @param {string} instanceUrl - The base URL of the Magentrix instance (e.g. "https://your.magentrix.com").
40
- * @param {string} token - The OAuth2 bearer token for authentication.
41
- * @param {string} entityName - The Magentrix entity type. Allowed: "ActiveClass" or "ActivePage" (case-insensitive).
42
- * @param {string} recordId - The unique Magentrix record ID to retrieve.
43
- * @returns {Promise<Object>} The API response object containing the record data.
44
- * @throws {Error} If required parameters are missing, entityName is invalid, or recordId is not provided.
45
- *
46
- * @example
47
- * const record = await retrieveEntity(
48
- * "https://your.magentrix.com",
49
- * "yourToken",
50
- * "ActiveClass",
51
- * "06bdc45e-8222-44f5-9ed2-40f5a7bc6cb3"
52
- * );
53
- */
54
- export const retrieveEntity = async (instanceUrl, token, entityName, recordId) => {
55
- // --- Validate required parameters ---
56
- if (!instanceUrl || typeof instanceUrl !== 'string') {
57
- throw new Error('Missing or invalid Magentrix instanceUrl');
58
- }
59
- if (!token || typeof token !== 'string') {
60
- throw new Error('Missing or invalid Magentrix token');
61
- }
62
- if (!entityName || typeof entityName !== 'string') {
63
- throw new Error("Missing or invalid 'entityName' (must be 'ActiveClass' or 'ActivePage')");
64
- }
65
- if (!recordId || typeof recordId !== 'string') {
66
- throw new Error("Missing or invalid 'recordId' (must be a Magentrix record GUID string)");
67
- }
68
-
69
- // --- Validate entity type ---
70
- const allowedEntities = ['activeclass', 'activepage'];
71
- const entity = entityName.trim().toLowerCase();
72
- if (!allowedEntities.includes(entity)) {
73
- throw new Error("Invalid 'entityName'. Allowed: 'ActiveClass' or 'ActivePage'");
74
- }
75
-
76
- // --- Make GET request to Magentrix API ---
77
- const response = await fetchMagentrix({
78
- instanceUrl,
79
- token,
80
- path: `/api/3.0/entity/${entity}/${recordId}`,
81
- method: "GET",
82
- returnErrorObject: true
83
- });
84
-
85
- return response;
86
- };
1
+ import { fetchMagentrix } from "../fetch.js";
2
+
3
+ /**
4
+ * Lists all entities available from the Magentrix API.
5
+ *
6
+ * Makes an authenticated GET request to `/api/3.0/entity` to retrieve metadata
7
+ * about all available entities (objects) in the Magentrix instance.
8
+ *
9
+ * Handles and throws both network errors and API-level errors with detailed messages.
10
+ *
11
+ * @async
12
+ * @param {string} instanceUrl - The base URL of the Magentrix instance.
13
+ * @param {string} token - The OAuth or access token for authentication.
14
+ * @returns {Promise<Object>} Parsed JSON response with entities metadata.
15
+ * @throws {Error} If required arguments are missing, network error occurs,
16
+ * HTTP error is returned, or API-level errors are found in the response.
17
+ */
18
+ export const listEntities = async (instanceUrl, token) => {
19
+ // --- Validate required input parameters ---
20
+ if (!instanceUrl || !token) {
21
+ throw new Error('Missing required Magentrix instanceUrl or token');
22
+ }
23
+
24
+ const data = await fetchMagentrix({
25
+ instanceUrl,
26
+ token,
27
+ path: '/api/3.0/entity',
28
+ method: "GET"
29
+ })
30
+
31
+ return data;
32
+ };
33
+
34
+ /**
35
+ * Retrieves a specific ActiveClass or ActivePage entity by ID from Magentrix via the REST API.
36
+ *
37
+ * @async
38
+ * @function retrieveEntity
39
+ * @param {string} instanceUrl - The base URL of the Magentrix instance (e.g. "https://your.magentrix.com").
40
+ * @param {string} token - The OAuth2 bearer token for authentication.
41
+ * @param {string} entityName - The Magentrix entity type. Allowed: "ActiveClass" or "ActivePage" (case-insensitive).
42
+ * @param {string} recordId - The unique Magentrix record ID to retrieve.
43
+ * @returns {Promise<Object>} The API response object containing the record data.
44
+ * @throws {Error} If required parameters are missing, entityName is invalid, or recordId is not provided.
45
+ *
46
+ * @example
47
+ * const record = await retrieveEntity(
48
+ * "https://your.magentrix.com",
49
+ * "yourToken",
50
+ * "ActiveClass",
51
+ * "06bdc45e-8222-44f5-9ed2-40f5a7bc6cb3"
52
+ * );
53
+ */
54
+ export const retrieveEntity = async (instanceUrl, token, entityName, recordId) => {
55
+ // --- Validate required parameters ---
56
+ if (!instanceUrl || typeof instanceUrl !== 'string') {
57
+ throw new Error('Missing or invalid Magentrix instanceUrl');
58
+ }
59
+ if (!token || typeof token !== 'string') {
60
+ throw new Error('Missing or invalid Magentrix token');
61
+ }
62
+ if (!entityName || typeof entityName !== 'string') {
63
+ throw new Error("Missing or invalid 'entityName' (must be 'ActiveClass' or 'ActivePage')");
64
+ }
65
+ if (!recordId || typeof recordId !== 'string') {
66
+ throw new Error("Missing or invalid 'recordId' (must be a Magentrix record GUID string)");
67
+ }
68
+
69
+ // --- Validate entity type ---
70
+ const allowedEntities = ['activeclass', 'activepage'];
71
+ const entity = entityName.trim().toLowerCase();
72
+ if (!allowedEntities.includes(entity)) {
73
+ throw new Error("Invalid 'entityName'. Allowed: 'ActiveClass' or 'ActivePage'");
74
+ }
75
+
76
+ // --- Make GET request to Magentrix API ---
77
+ const response = await fetchMagentrix({
78
+ instanceUrl,
79
+ token,
80
+ path: `/api/3.0/entity/${entity}/${recordId}`,
81
+ method: "GET",
82
+ returnErrorObject: true
83
+ });
84
+
85
+ return response;
86
+ };
@@ -1,66 +1,66 @@
1
- import { fetchMagentrix } from "../fetch.js";
2
-
3
- /**
4
- * Updates an existing ActiveClass or ActivePage entity in Magentrix via the REST API.
5
- *
6
- * @async
7
- * @function updateEntity
8
- * @param {string} instanceUrl - The base URL of the Magentrix instance (e.g. "https://your.magentrix.com").
9
- * @param {string} token - The OAuth2 bearer token for authentication.
10
- * @param {string} entityName - The Magentrix entity type. Allowed: "ActiveClass" or "ActivePage" (case-insensitive).
11
- * @param {string} recordId - The unique Magentrix record ID to update.
12
- * @param {Object} data - The fields to update on the entity. Provide any subset of updatable fields (e.g. Name, Description, Body, Type, etc).
13
- * @returns {Promise<Object>} The API response object containing updated record data.
14
- * @throws {Error} If required parameters are missing, entityName is invalid, recordId is not provided, or data is not an object.
15
- *
16
- * @example
17
- * const updated = await updateEntity(
18
- * "https://your.magentrix.com",
19
- * "yourToken",
20
- * "ActiveClass",
21
- * "06bdc45e-8222-44f5-9ed2-40f5a7bc6cb3",
22
- * { Name: "RenamedClass", Description: "Updated" }
23
- * );
24
- */
25
- export const updateEntity = async (instanceUrl, token, entityName, recordId, data) => {
26
- // --- Validate required parameters ---
27
- if (!instanceUrl || typeof instanceUrl !== 'string') {
28
- throw new Error('Missing or invalid Magentrix instanceUrl');
29
- }
30
- if (!token || typeof token !== 'string') {
31
- throw new Error('Missing or invalid Magentrix token');
32
- }
33
- if (!entityName || typeof entityName !== 'string') {
34
- throw new Error("Missing or invalid 'entityName' (must be 'ActiveClass' or 'ActivePage')");
35
- }
36
- if (!recordId || typeof recordId !== 'string') {
37
- throw new Error("Missing or invalid 'recordId' (must be a Magentrix record GUID string)");
38
- }
39
-
40
- // --- Validate entity type ---
41
- const allowedEntities = ['activeclass', 'activepage'];
42
- const entity = entityName.trim().toLowerCase();
43
- if (!allowedEntities.includes(entity)) {
44
- throw new Error("Invalid 'entityName'. Allowed: 'ActiveClass' or 'ActivePage'");
45
- }
46
-
47
- // --- Validate update data ---
48
- if (!data || typeof data !== 'object' || Array.isArray(data)) {
49
- throw new Error('Missing or invalid data object for entity update');
50
- }
51
- if (Object.keys(data).length === 0) {
52
- throw new Error('No fields provided to update');
53
- }
54
-
55
- // --- Make PATCH request to Magentrix API ---
56
- const response = await fetchMagentrix({
57
- instanceUrl,
58
- token,
59
- path: `/api/3.0/entity/${entity}/${recordId}`,
60
- method: "PATCH",
61
- body: data,
62
- returnErrorObject: true
63
- });
64
-
65
- return response;
66
- };
1
+ import { fetchMagentrix } from "../fetch.js";
2
+
3
+ /**
4
+ * Updates an existing ActiveClass or ActivePage entity in Magentrix via the REST API.
5
+ *
6
+ * @async
7
+ * @function updateEntity
8
+ * @param {string} instanceUrl - The base URL of the Magentrix instance (e.g. "https://your.magentrix.com").
9
+ * @param {string} token - The OAuth2 bearer token for authentication.
10
+ * @param {string} entityName - The Magentrix entity type. Allowed: "ActiveClass" or "ActivePage" (case-insensitive).
11
+ * @param {string} recordId - The unique Magentrix record ID to update.
12
+ * @param {Object} data - The fields to update on the entity. Provide any subset of updatable fields (e.g. Name, Description, Body, Type, etc).
13
+ * @returns {Promise<Object>} The API response object containing updated record data.
14
+ * @throws {Error} If required parameters are missing, entityName is invalid, recordId is not provided, or data is not an object.
15
+ *
16
+ * @example
17
+ * const updated = await updateEntity(
18
+ * "https://your.magentrix.com",
19
+ * "yourToken",
20
+ * "ActiveClass",
21
+ * "06bdc45e-8222-44f5-9ed2-40f5a7bc6cb3",
22
+ * { Name: "RenamedClass", Description: "Updated" }
23
+ * );
24
+ */
25
+ export const updateEntity = async (instanceUrl, token, entityName, recordId, data) => {
26
+ // --- Validate required parameters ---
27
+ if (!instanceUrl || typeof instanceUrl !== 'string') {
28
+ throw new Error('Missing or invalid Magentrix instanceUrl');
29
+ }
30
+ if (!token || typeof token !== 'string') {
31
+ throw new Error('Missing or invalid Magentrix token');
32
+ }
33
+ if (!entityName || typeof entityName !== 'string') {
34
+ throw new Error("Missing or invalid 'entityName' (must be 'ActiveClass' or 'ActivePage')");
35
+ }
36
+ if (!recordId || typeof recordId !== 'string') {
37
+ throw new Error("Missing or invalid 'recordId' (must be a Magentrix record GUID string)");
38
+ }
39
+
40
+ // --- Validate entity type ---
41
+ const allowedEntities = ['activeclass', 'activepage'];
42
+ const entity = entityName.trim().toLowerCase();
43
+ if (!allowedEntities.includes(entity)) {
44
+ throw new Error("Invalid 'entityName'. Allowed: 'ActiveClass' or 'ActivePage'");
45
+ }
46
+
47
+ // --- Validate update data ---
48
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
49
+ throw new Error('Missing or invalid data object for entity update');
50
+ }
51
+ if (Object.keys(data).length === 0) {
52
+ throw new Error('No fields provided to update');
53
+ }
54
+
55
+ // --- Make PATCH request to Magentrix API ---
56
+ const response = await fetchMagentrix({
57
+ instanceUrl,
58
+ token,
59
+ path: `/api/3.0/entity/${entity}/${recordId}`,
60
+ method: "PATCH",
61
+ body: data,
62
+ returnErrorObject: true
63
+ });
64
+
65
+ return response;
66
+ };
@@ -1,168 +1,168 @@
1
- import debug from '../debug.js';
2
-
3
- /**
4
- * Checks if a request body should be JSON-stringified.
5
- * Excludes FormData, Blob, ArrayBuffer, URLSearchParams, and typed arrays.
6
- * @param {any} body
7
- * @returns {boolean}
8
- */
9
- function isJsonBody(body) {
10
- return (
11
- typeof body === 'object' &&
12
- body !== null &&
13
- !(body instanceof FormData) &&
14
- !(body instanceof Blob) &&
15
- !(body instanceof ArrayBuffer) &&
16
- !(body instanceof URLSearchParams) &&
17
- !ArrayBuffer.isView(body) // covers Uint8Array, etc.
18
- );
19
- }
20
-
21
- /**
22
- * Fetch helper for Magentrix API.
23
- * Handles network, HTTP, and API-level errors with detailed messages or returns error info as JSON object.
24
- *
25
- * @async
26
- * @function fetchMagentrix
27
- * @param {Object} opts - Fetch options.
28
- * @param {string} opts.instanceUrl - Magentrix instance base URL (e.g. https://your.magentrix.com).
29
- * @param {string} [opts.token] - OAuth2 bearer token for authentication (optional for public endpoints).
30
- * @param {string} opts.path - API path (e.g. '/api/3.0/entity/activeclass').
31
- * @param {string} [opts.method='GET'] - HTTP method.
32
- * @param {any} [opts.body] - Request body (object for JSON, or raw for FormData, Blob, string, etc).
33
- * @param {Object} [opts.headers] - Additional headers to merge with defaults.
34
- * @param {boolean} [opts.returnErrorObject=false] - If true, errors are returned as JSON objects instead of thrown as Error.
35
- * @returns {Promise<Object>} Parsed JSON response from the API if successful.
36
- * @throws {Error|Object} Throws Error (default) or error object if returnErrorObject is true.
37
- */
38
- export const fetchMagentrix = async ({
39
- instanceUrl,
40
- token,
41
- path,
42
- method = 'GET',
43
- body,
44
- headers = {},
45
- ignoreContentType = false,
46
- returnErrorObject = false,
47
- errorConfig = {
48
- includeStatus: false,
49
- includeURL: false,
50
- label: '', // 'Magentrix errors:',
51
- bullets: false
52
- },
53
- }) => {
54
- if (!instanceUrl || !path) {
55
- const err = { type: 'client', message: 'Missing required parameter(s): instanceUrl or path' };
56
- if (returnErrorObject) throw err;
57
- throw new Error(err.message);
58
- }
59
-
60
- const finalHeaders = {
61
- 'Accept': 'application/json',
62
- ...headers
63
- };
64
- if (isJsonBody(body)) finalHeaders['Content-Type'] = 'application/json';
65
- if (token) finalHeaders['Authorization'] = `Bearer ${token}`;
66
- let requestBody;
67
- if (body === undefined || body === null) {
68
- requestBody = undefined;
69
- } else if (isJsonBody(body)) {
70
- requestBody = JSON.stringify(body);
71
- } else {
72
- requestBody = body;
73
- }
74
- if (!finalHeaders['Content-Type'] && !ignoreContentType) finalHeaders['Content-Type'] = 'application/json';
75
-
76
- const fullUrl = `${instanceUrl.replace(/\/$/, '')}${path}`;
77
- debug.request(method, fullUrl, finalHeaders, body);
78
-
79
- let response, responseData;
80
- try {
81
- response = await fetch(fullUrl, {
82
- method,
83
- headers: finalHeaders,
84
- body: requestBody
85
- });
86
- } catch (err) {
87
- const cause = err.cause || {};
88
- const rootDetail = cause.code ? `${cause.code}: ${cause.message || ''}` : '';
89
- debug.log('HTTP-ERR', `Network error: ${err.message}${rootDetail ? ` (${rootDetail})` : ''}`);
90
- if (cause.code) debug.log('HTTP-ERR', `Error code: ${cause.code}, hostname: ${cause.hostname || 'N/A'}`);
91
- if (err.stack) debug.log('HTTP-ERR', `Stack: ${err.stack}`);
92
- const errorObj = {
93
- type: 'network',
94
- message: `Network error contacting Magentrix API: ${err.message}`,
95
- error: err
96
- };
97
- if (returnErrorObject) throw errorObj;
98
- throw new Error(errorObj.message);
99
- }
100
-
101
- try {
102
- responseData = await response.json();
103
- } catch {
104
- responseData = null;
105
- }
106
-
107
- debug.response(response.status, response.statusText, response.headers, responseData);
108
-
109
- if (!response.ok) {
110
- const errorObj = {
111
- type: 'http',
112
- status: response.status,
113
- statusText: response.statusText,
114
- url: response.url,
115
- response: responseData,
116
- };
117
- // Optionally add detailed error message
118
- let msg = errorConfig?.includeStatus ? `HTTP ${response.status} ${response.statusText}\n` : '';
119
- if (responseData) {
120
- const responseErrs = responseData.errors || responseData.Errors;
121
-
122
- if (Array.isArray(responseErrs) && responseErrs.length) {
123
- msg += `${errorConfig?.label}${errorConfig?.label ? '\n' : ''}` + responseErrs.map(e => `${errorConfig?.bullets ? " • " : ""}${e.code ? `[${e.code || '500'}] ` : ''}${e.message || e}`).join('\n');
124
- } else if (responseData.message) {
125
- msg += `Magentrix message: ${responseData.message}`;
126
- } else {
127
- msg += JSON.stringify(responseData);
128
- }
129
- }
130
- if (errorConfig?.includeURL) msg += `\nURL: ${response.url}`;
131
- errorObj.message = msg;
132
- debug.log('HTTP-ERR', `HTTP ${response.status}: ${msg}`);
133
- if (returnErrorObject) throw errorObj;
134
- throw new Error(msg);
135
- }
136
-
137
- // Handle API-level business logic errors
138
- if (
139
- !responseData ||
140
- responseData.success === false ||
141
- (Array.isArray(responseData?.errors) && responseData.errors.length > 0) ||
142
- responseData.error
143
- ) {
144
- const errorObj = {
145
- type: 'api',
146
- url: response.url,
147
- response: responseData
148
- };
149
- let details = '';
150
- if (Array.isArray(responseData?.errors) && responseData.errors.length) {
151
- details = responseData.errors
152
- .map(e => ` • ${e.code ? `[${e.code}] ` : ''}${e.message}`)
153
- .join('\n');
154
- } else if (responseData?.message) {
155
- details = responseData.message;
156
- } else if (typeof responseData === 'object') {
157
- details = JSON.stringify(responseData);
158
- } else {
159
- details = String(responseData);
160
- }
161
- errorObj.message = `Magentrix API error:\n${details}`;
162
- debug.log('API-ERR', errorObj.message);
163
- if (returnErrorObject) throw errorObj;
164
- throw new Error(errorObj.message);
165
- }
166
-
167
- return responseData;
168
- };
1
+ import debug from '../debug.js';
2
+
3
+ /**
4
+ * Checks if a request body should be JSON-stringified.
5
+ * Excludes FormData, Blob, ArrayBuffer, URLSearchParams, and typed arrays.
6
+ * @param {any} body
7
+ * @returns {boolean}
8
+ */
9
+ function isJsonBody(body) {
10
+ return (
11
+ typeof body === 'object' &&
12
+ body !== null &&
13
+ !(body instanceof FormData) &&
14
+ !(body instanceof Blob) &&
15
+ !(body instanceof ArrayBuffer) &&
16
+ !(body instanceof URLSearchParams) &&
17
+ !ArrayBuffer.isView(body) // covers Uint8Array, etc.
18
+ );
19
+ }
20
+
21
+ /**
22
+ * Fetch helper for Magentrix API.
23
+ * Handles network, HTTP, and API-level errors with detailed messages or returns error info as JSON object.
24
+ *
25
+ * @async
26
+ * @function fetchMagentrix
27
+ * @param {Object} opts - Fetch options.
28
+ * @param {string} opts.instanceUrl - Magentrix instance base URL (e.g. https://your.magentrix.com).
29
+ * @param {string} [opts.token] - OAuth2 bearer token for authentication (optional for public endpoints).
30
+ * @param {string} opts.path - API path (e.g. '/api/3.0/entity/activeclass').
31
+ * @param {string} [opts.method='GET'] - HTTP method.
32
+ * @param {any} [opts.body] - Request body (object for JSON, or raw for FormData, Blob, string, etc).
33
+ * @param {Object} [opts.headers] - Additional headers to merge with defaults.
34
+ * @param {boolean} [opts.returnErrorObject=false] - If true, errors are returned as JSON objects instead of thrown as Error.
35
+ * @returns {Promise<Object>} Parsed JSON response from the API if successful.
36
+ * @throws {Error|Object} Throws Error (default) or error object if returnErrorObject is true.
37
+ */
38
+ export const fetchMagentrix = async ({
39
+ instanceUrl,
40
+ token,
41
+ path,
42
+ method = 'GET',
43
+ body,
44
+ headers = {},
45
+ ignoreContentType = false,
46
+ returnErrorObject = false,
47
+ errorConfig = {
48
+ includeStatus: false,
49
+ includeURL: false,
50
+ label: '', // 'Magentrix errors:',
51
+ bullets: false
52
+ },
53
+ }) => {
54
+ if (!instanceUrl || !path) {
55
+ const err = { type: 'client', message: 'Missing required parameter(s): instanceUrl or path' };
56
+ if (returnErrorObject) throw err;
57
+ throw new Error(err.message);
58
+ }
59
+
60
+ const finalHeaders = {
61
+ 'Accept': 'application/json',
62
+ ...headers
63
+ };
64
+ if (isJsonBody(body)) finalHeaders['Content-Type'] = 'application/json';
65
+ if (token) finalHeaders['Authorization'] = `Bearer ${token}`;
66
+ let requestBody;
67
+ if (body === undefined || body === null) {
68
+ requestBody = undefined;
69
+ } else if (isJsonBody(body)) {
70
+ requestBody = JSON.stringify(body);
71
+ } else {
72
+ requestBody = body;
73
+ }
74
+ if (!finalHeaders['Content-Type'] && !ignoreContentType) finalHeaders['Content-Type'] = 'application/json';
75
+
76
+ const fullUrl = `${instanceUrl.replace(/\/$/, '')}${path}`;
77
+ debug.request(method, fullUrl, finalHeaders, body);
78
+
79
+ let response, responseData;
80
+ try {
81
+ response = await fetch(fullUrl, {
82
+ method,
83
+ headers: finalHeaders,
84
+ body: requestBody
85
+ });
86
+ } catch (err) {
87
+ const cause = err.cause || {};
88
+ const rootDetail = cause.code ? `${cause.code}: ${cause.message || ''}` : '';
89
+ debug.log('HTTP-ERR', `Network error: ${err.message}${rootDetail ? ` (${rootDetail})` : ''}`);
90
+ if (cause.code) debug.log('HTTP-ERR', `Error code: ${cause.code}, hostname: ${cause.hostname || 'N/A'}`);
91
+ if (err.stack) debug.log('HTTP-ERR', `Stack: ${err.stack}`);
92
+ const errorObj = {
93
+ type: 'network',
94
+ message: `Network error contacting Magentrix API: ${err.message}`,
95
+ error: err
96
+ };
97
+ if (returnErrorObject) throw errorObj;
98
+ throw new Error(errorObj.message);
99
+ }
100
+
101
+ try {
102
+ responseData = await response.json();
103
+ } catch {
104
+ responseData = null;
105
+ }
106
+
107
+ debug.response(response.status, response.statusText, response.headers, responseData);
108
+
109
+ if (!response.ok) {
110
+ const errorObj = {
111
+ type: 'http',
112
+ status: response.status,
113
+ statusText: response.statusText,
114
+ url: response.url,
115
+ response: responseData,
116
+ };
117
+ // Optionally add detailed error message
118
+ let msg = errorConfig?.includeStatus ? `HTTP ${response.status} ${response.statusText}\n` : '';
119
+ if (responseData) {
120
+ const responseErrs = responseData.errors || responseData.Errors;
121
+
122
+ if (Array.isArray(responseErrs) && responseErrs.length) {
123
+ msg += `${errorConfig?.label}${errorConfig?.label ? '\n' : ''}` + responseErrs.map(e => `${errorConfig?.bullets ? " • " : ""}${e.code ? `[${e.code || '500'}] ` : ''}${e.message || e}`).join('\n');
124
+ } else if (responseData.message) {
125
+ msg += `Magentrix message: ${responseData.message}`;
126
+ } else {
127
+ msg += JSON.stringify(responseData);
128
+ }
129
+ }
130
+ if (errorConfig?.includeURL) msg += `\nURL: ${response.url}`;
131
+ errorObj.message = msg;
132
+ debug.log('HTTP-ERR', `HTTP ${response.status}: ${msg}`);
133
+ if (returnErrorObject) throw errorObj;
134
+ throw new Error(msg);
135
+ }
136
+
137
+ // Handle API-level business logic errors
138
+ if (
139
+ !responseData ||
140
+ responseData.success === false ||
141
+ (Array.isArray(responseData?.errors) && responseData.errors.length > 0) ||
142
+ responseData.error
143
+ ) {
144
+ const errorObj = {
145
+ type: 'api',
146
+ url: response.url,
147
+ response: responseData
148
+ };
149
+ let details = '';
150
+ if (Array.isArray(responseData?.errors) && responseData.errors.length) {
151
+ details = responseData.errors
152
+ .map(e => ` • ${e.code ? `[${e.code}] ` : ''}${e.message}`)
153
+ .join('\n');
154
+ } else if (responseData?.message) {
155
+ details = responseData.message;
156
+ } else if (typeof responseData === 'object') {
157
+ details = JSON.stringify(responseData);
158
+ } else {
159
+ details = String(responseData);
160
+ }
161
+ errorObj.message = `Magentrix API error:\n${details}`;
162
+ debug.log('API-ERR', errorObj.message);
163
+ if (returnErrorObject) throw errorObj;
164
+ throw new Error(errorObj.message);
165
+ }
166
+
167
+ return responseData;
168
+ };