@magentrix-corp/magentrix-cli 1.3.15 → 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 -45
  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,251 +1,251 @@
1
- import { fetchMagentrix } from "../fetch.js";
2
- import { File } from "node:buffer";
3
-
4
- /**
5
- * List all deployed Iris applications.
6
- * @param {string} instanceUrl - Magentrix instance base URL
7
- * @param {string} token - OAuth2 bearer token
8
- * @returns {Promise<{success: boolean, apps: Array<{folderName: string, uploadedOn: string, modifiedOn: string, size: number}>}>}
9
- */
10
- export const listApps = async (instanceUrl, token) => {
11
- if (!instanceUrl || !token) {
12
- throw new Error('Missing required Magentrix instanceUrl or token');
13
- }
14
-
15
- const response = await fetchMagentrix({
16
- instanceUrl,
17
- token,
18
- path: '/iris/listapps',
19
- method: 'GET',
20
- returnErrorObject: true
21
- });
22
-
23
- return response;
24
- };
25
-
26
- /**
27
- * Publish (upload) an Iris Vue.js application.
28
- * @param {string} instanceUrl - Magentrix instance base URL
29
- * @param {string} token - OAuth2 bearer token
30
- * @param {Buffer} zipBuffer - The zip file as a Buffer
31
- * @param {string} filename - The filename for the zip (e.g., "my-app.zip")
32
- * @param {string} appName - The user-friendly display name (required for navigation)
33
- * @param {Object} options - Optional parameters
34
- * @param {string} [options.appDescription] - App description
35
- * @param {string} [options.appIconId] - App icon ID
36
- * @returns {Promise<{success: boolean, message: string, folderName: string}>}
37
- */
38
- export const publishApp = async (instanceUrl, token, zipBuffer, filename, appName, options = {}) => {
39
- if (!instanceUrl || !token) {
40
- throw new Error('Missing required Magentrix instanceUrl or token');
41
- }
42
-
43
- if (!zipBuffer || !Buffer.isBuffer(zipBuffer)) {
44
- throw new Error('zipBuffer must be a valid Buffer');
45
- }
46
-
47
- if (!filename) {
48
- throw new Error('filename is required');
49
- }
50
-
51
- // Create a File object from the buffer for FormData
52
- const file = new File([zipBuffer], filename, { type: 'application/zip' });
53
-
54
- const formData = new FormData();
55
- formData.append('file', file);
56
-
57
- // Build query parameters
58
- // Note: app-name is only sent when provided (required for creates, optional for updates)
59
- // Sending app-name on updates can trigger a rename attempt that fails if the user
60
- // lacks metadata-edit permissions on the server
61
- const params = new URLSearchParams();
62
-
63
- if (appName) {
64
- params.append('app-name', appName);
65
- }
66
-
67
- if (options.appDescription) {
68
- params.append('app-description', options.appDescription);
69
- }
70
-
71
- if (options.appIconId) {
72
- params.append('app-icon-id', options.appIconId);
73
- }
74
-
75
- const response = await fetchMagentrix({
76
- instanceUrl,
77
- token,
78
- path: `/iris/publishapp?${params.toString()}`,
79
- method: 'POST',
80
- body: formData,
81
- ignoreContentType: true,
82
- returnErrorObject: true
83
- });
84
-
85
- return response;
86
- };
87
-
88
- /**
89
- * Delete an Iris application.
90
- * @param {string} instanceUrl - Magentrix instance base URL
91
- * @param {string} token - OAuth2 bearer token
92
- * @param {string} folderName - The folder name of the app to delete
93
- * @returns {Promise<{success: boolean, message: string}>}
94
- */
95
- export const deleteApp = async (instanceUrl, token, folderName) => {
96
- if (!instanceUrl || !token) {
97
- throw new Error('Missing required Magentrix instanceUrl or token');
98
- }
99
-
100
- if (!folderName) {
101
- throw new Error('folderName is required');
102
- }
103
-
104
- const response = await fetchMagentrix({
105
- instanceUrl,
106
- token,
107
- path: `/iris/deleteapp?folderName=${encodeURIComponent(folderName)}`,
108
- method: 'POST',
109
- returnErrorObject: true
110
- });
111
-
112
- return response;
113
- };
114
-
115
- /**
116
- * Download a single Iris application as a buffer.
117
- * @param {string} instanceUrl - Magentrix instance base URL
118
- * @param {string} token - OAuth2 bearer token
119
- * @param {string} folderName - The folder name of the app to download
120
- * @returns {Promise<{buffer: Buffer, filename: string}>}
121
- */
122
- export const downloadApp = async (instanceUrl, token, folderName) => {
123
- if (!instanceUrl || !token) {
124
- throw new Error('Missing required Magentrix instanceUrl or token');
125
- }
126
-
127
- if (!folderName) {
128
- throw new Error('folderName is required');
129
- }
130
-
131
- const url = `${instanceUrl.replace(/\/$/, '')}/iris/downloadapp?folderName=${encodeURIComponent(folderName)}`;
132
-
133
- const response = await fetch(url, {
134
- method: 'GET',
135
- headers: {
136
- 'Authorization': `Bearer ${token}`
137
- }
138
- });
139
-
140
- if (!response.ok) {
141
- let errorMessage = `Download failed (${response.status})`;
142
- try {
143
- const errorData = await response.json();
144
- if (errorData.message) {
145
- errorMessage = errorData.message;
146
- }
147
- } catch {
148
- errorMessage = `Download failed: ${response.statusText}`;
149
- }
150
- throw new Error(errorMessage);
151
- }
152
-
153
- // Get filename from Content-Disposition header
154
- const contentDisposition = response.headers.get('content-disposition') || '';
155
- const filenameMatch = /filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i.exec(contentDisposition);
156
- const filename = filenameMatch ? decodeURIComponent(filenameMatch[1]) : `${folderName}.zip`;
157
-
158
- const arrayBuffer = await response.arrayBuffer();
159
- const buffer = Buffer.from(arrayBuffer);
160
-
161
- return { buffer, filename };
162
- };
163
-
164
- /**
165
- * Download all Iris applications as a single zip buffer.
166
- * @param {string} instanceUrl - Magentrix instance base URL
167
- * @param {string} token - OAuth2 bearer token
168
- * @returns {Promise<{buffer: Buffer, filename: string}>}
169
- */
170
- export const downloadAllApps = async (instanceUrl, token) => {
171
- if (!instanceUrl || !token) {
172
- throw new Error('Missing required Magentrix instanceUrl or token');
173
- }
174
-
175
- const url = `${instanceUrl.replace(/\/$/, '')}/iris/downloadallapps`;
176
-
177
- const response = await fetch(url, {
178
- method: 'GET',
179
- headers: {
180
- 'Authorization': `Bearer ${token}`
181
- }
182
- });
183
-
184
- if (!response.ok) {
185
- let errorMessage = `Download failed (${response.status})`;
186
- try {
187
- const errorData = await response.json();
188
- if (errorData.message) {
189
- errorMessage = errorData.message;
190
- }
191
- } catch {
192
- errorMessage = `Download failed: ${response.statusText}`;
193
- }
194
- throw new Error(errorMessage);
195
- }
196
-
197
- // Get filename from Content-Disposition header
198
- const contentDisposition = response.headers.get('content-disposition') || '';
199
- const filenameMatch = /filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i.exec(contentDisposition);
200
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
201
- const filename = filenameMatch ? decodeURIComponent(filenameMatch[1]) : `iris-apps_${timestamp}.zip`;
202
-
203
- const arrayBuffer = await response.arrayBuffer();
204
- const buffer = Buffer.from(arrayBuffer);
205
-
206
- return { buffer, filename };
207
- };
208
-
209
- /**
210
- * Get platform assets for local development (CSS, fonts, etc.).
211
- * @param {string} siteUrl - Magentrix instance base URL (from Vue config.ts)
212
- * @param {string} token - OAuth2 bearer token
213
- * @returns {Promise<{success: boolean, assets: string[]}>}
214
- */
215
- export const getIrisAssets = async (siteUrl, token) => {
216
- if (!siteUrl) {
217
- throw new Error('Missing required siteUrl');
218
- }
219
-
220
- const url = `${siteUrl.replace(/\/$/, '')}/iris/getirisassets`;
221
-
222
- const headers = {
223
- 'Accept': 'application/json'
224
- };
225
-
226
- // Token is optional for this endpoint (might be public)
227
- if (token) {
228
- headers['Authorization'] = `Bearer ${token}`;
229
- }
230
-
231
- const response = await fetch(url, {
232
- method: 'GET',
233
- headers
234
- });
235
-
236
- if (!response.ok) {
237
- let errorMessage = `Failed to fetch Iris assets (${response.status})`;
238
- try {
239
- const errorData = await response.json();
240
- if (errorData.message) {
241
- errorMessage = errorData.message;
242
- }
243
- } catch {
244
- errorMessage = `Failed to fetch Iris assets: ${response.statusText}`;
245
- }
246
- throw new Error(errorMessage);
247
- }
248
-
249
- const data = await response.json();
250
- return data;
251
- };
1
+ import { fetchMagentrix } from "../fetch.js";
2
+ import { File } from "node:buffer";
3
+
4
+ /**
5
+ * List all deployed Iris applications.
6
+ * @param {string} instanceUrl - Magentrix instance base URL
7
+ * @param {string} token - OAuth2 bearer token
8
+ * @returns {Promise<{success: boolean, apps: Array<{folderName: string, uploadedOn: string, modifiedOn: string, size: number}>}>}
9
+ */
10
+ export const listApps = async (instanceUrl, token) => {
11
+ if (!instanceUrl || !token) {
12
+ throw new Error('Missing required Magentrix instanceUrl or token');
13
+ }
14
+
15
+ const response = await fetchMagentrix({
16
+ instanceUrl,
17
+ token,
18
+ path: '/iris/listapps',
19
+ method: 'GET',
20
+ returnErrorObject: true
21
+ });
22
+
23
+ return response;
24
+ };
25
+
26
+ /**
27
+ * Publish (upload) an Iris Vue.js application.
28
+ * @param {string} instanceUrl - Magentrix instance base URL
29
+ * @param {string} token - OAuth2 bearer token
30
+ * @param {Buffer} zipBuffer - The zip file as a Buffer
31
+ * @param {string} filename - The filename for the zip (e.g., "my-app.zip")
32
+ * @param {string} appName - The user-friendly display name (required for navigation)
33
+ * @param {Object} options - Optional parameters
34
+ * @param {string} [options.appDescription] - App description
35
+ * @param {string} [options.appIconId] - App icon ID
36
+ * @returns {Promise<{success: boolean, message: string, folderName: string}>}
37
+ */
38
+ export const publishApp = async (instanceUrl, token, zipBuffer, filename, appName, options = {}) => {
39
+ if (!instanceUrl || !token) {
40
+ throw new Error('Missing required Magentrix instanceUrl or token');
41
+ }
42
+
43
+ if (!zipBuffer || !Buffer.isBuffer(zipBuffer)) {
44
+ throw new Error('zipBuffer must be a valid Buffer');
45
+ }
46
+
47
+ if (!filename) {
48
+ throw new Error('filename is required');
49
+ }
50
+
51
+ // Create a File object from the buffer for FormData
52
+ const file = new File([zipBuffer], filename, { type: 'application/zip' });
53
+
54
+ const formData = new FormData();
55
+ formData.append('file', file);
56
+
57
+ // Build query parameters
58
+ // Note: app-name is only sent when provided (required for creates, optional for updates)
59
+ // Sending app-name on updates can trigger a rename attempt that fails if the user
60
+ // lacks metadata-edit permissions on the server
61
+ const params = new URLSearchParams();
62
+
63
+ if (appName) {
64
+ params.append('app-name', appName);
65
+ }
66
+
67
+ if (options.appDescription) {
68
+ params.append('app-description', options.appDescription);
69
+ }
70
+
71
+ if (options.appIconId) {
72
+ params.append('app-icon-id', options.appIconId);
73
+ }
74
+
75
+ const response = await fetchMagentrix({
76
+ instanceUrl,
77
+ token,
78
+ path: `/iris/publishapp?${params.toString()}`,
79
+ method: 'POST',
80
+ body: formData,
81
+ ignoreContentType: true,
82
+ returnErrorObject: true
83
+ });
84
+
85
+ return response;
86
+ };
87
+
88
+ /**
89
+ * Delete an Iris application.
90
+ * @param {string} instanceUrl - Magentrix instance base URL
91
+ * @param {string} token - OAuth2 bearer token
92
+ * @param {string} folderName - The folder name of the app to delete
93
+ * @returns {Promise<{success: boolean, message: string}>}
94
+ */
95
+ export const deleteApp = async (instanceUrl, token, folderName) => {
96
+ if (!instanceUrl || !token) {
97
+ throw new Error('Missing required Magentrix instanceUrl or token');
98
+ }
99
+
100
+ if (!folderName) {
101
+ throw new Error('folderName is required');
102
+ }
103
+
104
+ const response = await fetchMagentrix({
105
+ instanceUrl,
106
+ token,
107
+ path: `/iris/deleteapp?folderName=${encodeURIComponent(folderName)}`,
108
+ method: 'POST',
109
+ returnErrorObject: true
110
+ });
111
+
112
+ return response;
113
+ };
114
+
115
+ /**
116
+ * Download a single Iris application as a buffer.
117
+ * @param {string} instanceUrl - Magentrix instance base URL
118
+ * @param {string} token - OAuth2 bearer token
119
+ * @param {string} folderName - The folder name of the app to download
120
+ * @returns {Promise<{buffer: Buffer, filename: string}>}
121
+ */
122
+ export const downloadApp = async (instanceUrl, token, folderName) => {
123
+ if (!instanceUrl || !token) {
124
+ throw new Error('Missing required Magentrix instanceUrl or token');
125
+ }
126
+
127
+ if (!folderName) {
128
+ throw new Error('folderName is required');
129
+ }
130
+
131
+ const url = `${instanceUrl.replace(/\/$/, '')}/iris/downloadapp?folderName=${encodeURIComponent(folderName)}`;
132
+
133
+ const response = await fetch(url, {
134
+ method: 'GET',
135
+ headers: {
136
+ 'Authorization': `Bearer ${token}`
137
+ }
138
+ });
139
+
140
+ if (!response.ok) {
141
+ let errorMessage = `Download failed (${response.status})`;
142
+ try {
143
+ const errorData = await response.json();
144
+ if (errorData.message) {
145
+ errorMessage = errorData.message;
146
+ }
147
+ } catch {
148
+ errorMessage = `Download failed: ${response.statusText}`;
149
+ }
150
+ throw new Error(errorMessage);
151
+ }
152
+
153
+ // Get filename from Content-Disposition header
154
+ const contentDisposition = response.headers.get('content-disposition') || '';
155
+ const filenameMatch = /filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i.exec(contentDisposition);
156
+ const filename = filenameMatch ? decodeURIComponent(filenameMatch[1]) : `${folderName}.zip`;
157
+
158
+ const arrayBuffer = await response.arrayBuffer();
159
+ const buffer = Buffer.from(arrayBuffer);
160
+
161
+ return { buffer, filename };
162
+ };
163
+
164
+ /**
165
+ * Download all Iris applications as a single zip buffer.
166
+ * @param {string} instanceUrl - Magentrix instance base URL
167
+ * @param {string} token - OAuth2 bearer token
168
+ * @returns {Promise<{buffer: Buffer, filename: string}>}
169
+ */
170
+ export const downloadAllApps = async (instanceUrl, token) => {
171
+ if (!instanceUrl || !token) {
172
+ throw new Error('Missing required Magentrix instanceUrl or token');
173
+ }
174
+
175
+ const url = `${instanceUrl.replace(/\/$/, '')}/iris/downloadallapps`;
176
+
177
+ const response = await fetch(url, {
178
+ method: 'GET',
179
+ headers: {
180
+ 'Authorization': `Bearer ${token}`
181
+ }
182
+ });
183
+
184
+ if (!response.ok) {
185
+ let errorMessage = `Download failed (${response.status})`;
186
+ try {
187
+ const errorData = await response.json();
188
+ if (errorData.message) {
189
+ errorMessage = errorData.message;
190
+ }
191
+ } catch {
192
+ errorMessage = `Download failed: ${response.statusText}`;
193
+ }
194
+ throw new Error(errorMessage);
195
+ }
196
+
197
+ // Get filename from Content-Disposition header
198
+ const contentDisposition = response.headers.get('content-disposition') || '';
199
+ const filenameMatch = /filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i.exec(contentDisposition);
200
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
201
+ const filename = filenameMatch ? decodeURIComponent(filenameMatch[1]) : `iris-apps_${timestamp}.zip`;
202
+
203
+ const arrayBuffer = await response.arrayBuffer();
204
+ const buffer = Buffer.from(arrayBuffer);
205
+
206
+ return { buffer, filename };
207
+ };
208
+
209
+ /**
210
+ * Get platform assets for local development (CSS, fonts, etc.).
211
+ * @param {string} siteUrl - Magentrix instance base URL (from Vue config.ts)
212
+ * @param {string} token - OAuth2 bearer token
213
+ * @returns {Promise<{success: boolean, assets: string[]}>}
214
+ */
215
+ export const getIrisAssets = async (siteUrl, token) => {
216
+ if (!siteUrl) {
217
+ throw new Error('Missing required siteUrl');
218
+ }
219
+
220
+ const url = `${siteUrl.replace(/\/$/, '')}/iris/getirisassets`;
221
+
222
+ const headers = {
223
+ 'Accept': 'application/json'
224
+ };
225
+
226
+ // Token is optional for this endpoint (might be public)
227
+ if (token) {
228
+ headers['Authorization'] = `Bearer ${token}`;
229
+ }
230
+
231
+ const response = await fetch(url, {
232
+ method: 'GET',
233
+ headers
234
+ });
235
+
236
+ if (!response.ok) {
237
+ let errorMessage = `Failed to fetch Iris assets (${response.status})`;
238
+ try {
239
+ const errorData = await response.json();
240
+ if (errorData.message) {
241
+ errorMessage = errorData.message;
242
+ }
243
+ } catch {
244
+ errorMessage = `Failed to fetch Iris assets: ${response.statusText}`;
245
+ }
246
+ throw new Error(errorMessage);
247
+ }
248
+
249
+ const data = await response.json();
250
+ return data;
251
+ };
@@ -1,36 +1,36 @@
1
- import { fetchMagentrix } from "../fetch.js";
2
- import debug from '../../debug.js';
3
-
4
- /**
5
- * Executes a Magentrix Entity Query Language (MEQL) query via the REST API v3.
6
- *
7
- * @async
8
- * @param {string} instanceUrl - Base URL of the Magentrix instance.
9
- * @param {string} token - Bearer token string.
10
- * @param {string} query - MEQL query string.
11
- * @returns {Promise<object>} The API response data.
12
- * @throws {Error} On HTTP or API-level error.
13
- */
14
- export const meqlQuery = async (instanceUrl, token, query = '') => {
15
- if (!instanceUrl || !token) {
16
- throw new Error('Missing required Magentrix instanceUrl or token');
17
- }
18
- if (!query.trim()) {
19
- throw new Error('MEQL query string is required');
20
- }
21
-
22
- debug.log('MEQL', `Query: ${query}`);
23
-
24
- const data = await fetchMagentrix({
25
- instanceUrl,
26
- token,
27
- method: "POST",
28
- path: '/api/3.0/query',
29
- body: query
30
- })
31
-
32
- debug.log('MEQL', `Result: ${data?.Records?.length ?? 0} records returned`);
33
-
34
- // --- Success ---
35
- return data;
36
- };
1
+ import { fetchMagentrix } from "../fetch.js";
2
+ import debug from '../../debug.js';
3
+
4
+ /**
5
+ * Executes a Magentrix Entity Query Language (MEQL) query via the REST API v3.
6
+ *
7
+ * @async
8
+ * @param {string} instanceUrl - Base URL of the Magentrix instance.
9
+ * @param {string} token - Bearer token string.
10
+ * @param {string} query - MEQL query string.
11
+ * @returns {Promise<object>} The API response data.
12
+ * @throws {Error} On HTTP or API-level error.
13
+ */
14
+ export const meqlQuery = async (instanceUrl, token, query = '') => {
15
+ if (!instanceUrl || !token) {
16
+ throw new Error('Missing required Magentrix instanceUrl or token');
17
+ }
18
+ if (!query.trim()) {
19
+ throw new Error('MEQL query string is required');
20
+ }
21
+
22
+ debug.log('MEQL', `Query: ${query}`);
23
+
24
+ const data = await fetchMagentrix({
25
+ instanceUrl,
26
+ token,
27
+ method: "POST",
28
+ path: '/api/3.0/query',
29
+ body: query
30
+ })
31
+
32
+ debug.log('MEQL', `Result: ${data?.Records?.length ?? 0} records returned`);
33
+
34
+ // --- Success ---
35
+ return data;
36
+ };