@foxtware/mineral 0.1.9 → 0.1.11

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 (159) hide show
  1. package/.creds.yml.sample +27 -6
  2. package/AGENTS.md +6 -1
  3. package/README.md +1 -1
  4. package/api/bleckmann/.gitkeep +0 -0
  5. package/api/bleckmann/bleckmann.constants.js +8 -0
  6. package/api/bleckmann/bleckmann.utils.js +32 -0
  7. package/api/bleckmann/bleckmannAsnGet.js +52 -0
  8. package/api/bleckmann/bleckmannAsnsGet.js +65 -0
  9. package/api/constants.js +2 -1
  10. package/api/github/github.constants.js +11 -0
  11. package/api/github/github.utils.js +65 -0
  12. package/api/github/githubGet.js +144 -0
  13. package/api/github/githubIssueCreate.js +71 -0
  14. package/api/github/githubIssueGet.js +65 -0
  15. package/api/github/githubIssueUpdate.js +71 -0
  16. package/api/github/githubIssuesGet.js +74 -0
  17. package/api/github/githubPullRequestCreate.js +74 -0
  18. package/api/github/githubPullRequestGet.js +65 -0
  19. package/api/github/githubPullRequestMerge.js +77 -0
  20. package/api/github/githubPullRequestUpdate.js +72 -0
  21. package/api/github/githubPullRequestsGet.js +74 -0
  22. package/api/github/githubRepoCreate.js +80 -0
  23. package/api/github/githubRepoDelete.js +60 -0
  24. package/api/github/githubRepoGet.js +61 -0
  25. package/api/github/githubRepoUpdate.js +68 -0
  26. package/api/github/githubReposGet.js +69 -0
  27. package/api/github/githubUserGet.js +64 -0
  28. package/api/linear/linear.utils.js +9 -14
  29. package/api/linear/linearIssuesGet.js +9 -9
  30. package/api/logiwa/logiwa.utils.js +27 -25
  31. package/api/logiwa/logiwaInventoriesGet.js +5 -3
  32. package/api/logiwa/logiwaOrderGet.js +4 -2
  33. package/api/logiwa/logiwaOrdersGet.js +5 -3
  34. package/api/logiwa/logiwaProductGet.js +4 -2
  35. package/api/logiwa/logiwaProductsGet.js +5 -3
  36. package/api/logiwa/logiwaReportGetAvailableToPromise.js +5 -3
  37. package/api/logiwa/logiwaReportGetInventoryCalculation.js +5 -3
  38. package/api/logiwa/logiwaReportGetInventorySnapshot.js +5 -3
  39. package/api/logiwa/logiwaReportGetTotalInventory.js +5 -3
  40. package/api/logiwa/logiwaWebhookStatusGet.js +4 -2
  41. package/api/logiwa/logiwaWebhookSubscribe.js +8 -6
  42. package/api/logiwa/logiwaWebhookUnsubscribe.js +4 -2
  43. package/api/logiwa/logiwaWebhooksGet.js +4 -2
  44. package/api/loop/loop.utils.js +9 -15
  45. package/api/loop/loopGet.js +20 -24
  46. package/api/loop/loopReturnGet.js +6 -6
  47. package/api/peoplevox/peoplevox.constants.js +1 -1
  48. package/api/peoplevox/peoplevox.sessions.js +0 -33
  49. package/api/peoplevox/peoplevox.utils.js +63 -19
  50. package/api/peoplevox/peoplevoxGetSingle.js +7 -5
  51. package/api/peoplevox/peoplevoxOrderEdit.js +7 -5
  52. package/api/peoplevox/peoplevoxReportGet.js +11 -9
  53. package/api/pipelineSteps.js +49 -0
  54. package/api/shopify/shopify.utils.js +93 -22
  55. package/api/shopify/shopifyBulkMutationDo.js +211 -0
  56. package/api/shopify/shopifyBulkOperationGet.js +86 -0
  57. package/api/shopify/shopifyBulkOperationRunMutation.js +83 -0
  58. package/api/shopify/shopifyBulkOperationRunQuery.js +70 -0
  59. package/api/shopify/shopifyBulkQueryDo.js +127 -0
  60. package/api/shopify/shopifyCheckoutAndAccountsConfigurationUpdate.js +79 -0
  61. package/api/shopify/shopifyCollectionGet.js +6 -6
  62. package/api/shopify/shopifyCustomerGet.js +6 -6
  63. package/api/shopify/shopifyGet.js +18 -13
  64. package/api/shopify/shopifyGetSingle.js +9 -9
  65. package/api/shopify/shopifyMutationDo.js +6 -6
  66. package/api/shopify/shopifyOrdersGet.js +11 -11
  67. package/api/shopify/shopifyStagedUploadCreate.js +96 -0
  68. package/api/shopify/shopifyTagsAddBulk.js +77 -0
  69. package/api/slack/slack.utils.js +10 -19
  70. package/api/slack/slackMessagePost.js +11 -11
  71. package/api/starshipit/starshipit.constants.js +8 -0
  72. package/api/starshipit/starshipit.utils.js +71 -0
  73. package/api/starshipit/starshipitAddressCreate.js +77 -0
  74. package/api/starshipit/starshipitAddressDelete.js +55 -0
  75. package/api/starshipit/starshipitAddressUpdate.js +69 -0
  76. package/api/starshipit/starshipitAddressesGet.js +52 -0
  77. package/api/starshipit/starshipitGet.js +146 -0
  78. package/api/starshipit/starshipitOrderDelete.js +102 -0
  79. package/api/starshipit/starshipitOrderGet.js +74 -0
  80. package/api/starshipit/starshipitOrderUpdate.js +109 -0
  81. package/api/starshipit/starshipitOrdersListShipped.js +64 -0
  82. package/api/starshipit/starshipitProductAdd.js +100 -0
  83. package/api/starshipit/starshipitProductsDelete.js +57 -0
  84. package/api/starshipit/starshipitProductsGet.js +49 -0
  85. package/api/starshipit/starshipitTrackingGet.js +69 -0
  86. package/api/stripe/_example.js +7 -7
  87. package/api/stripe/stripe.utils.js +9 -14
  88. package/api/stripe/stripeCardTokenCreate.js +11 -11
  89. package/api/stripe/stripeChargeCapture.js +11 -11
  90. package/api/stripe/stripeChargeCreate.js +11 -11
  91. package/api/stripe/stripeChargeGet.js +5 -5
  92. package/api/stripe/stripeChargesGet.js +6 -6
  93. package/api/stripe/stripeRefundCreate.js +10 -10
  94. package/api/stripe/stripeRefundGet.js +5 -5
  95. package/api/stripe/stripeRefundsGet.js +6 -6
  96. package/api/stripe/stripeTokenGet.js +5 -5
  97. package/api/stylearcade/stylearcade.constants.js +5 -0
  98. package/api/stylearcade/stylearcade.utils.js +40 -0
  99. package/api/stylearcade/stylearcadeDataGet.js +62 -0
  100. package/api/stylearcade/stylearcadeGet.js +133 -0
  101. package/api/stylearcade/stylearcadeMetadataGet.js +47 -0
  102. package/api/threeclicks/threeclicks.constants.js +8 -0
  103. package/api/threeclicks/threeclicks.utils.js +119 -0
  104. package/api/threeclicks/threeclicksCollectionsGet.js +40 -0
  105. package/api/threeclicks/threeclicksGet.js +138 -0
  106. package/api/threeclicks/threeclicksKeycodeCreate.js +55 -0
  107. package/api/threeclicks/threeclicksKeycodeGet.js +47 -0
  108. package/api/threeclicks/threeclicksPurchaseOrderCreate.js +34 -0
  109. package/api/threeclicks/threeclicksPurchaseOrderCreateBulk.js +34 -0
  110. package/api/threeclicks/threeclicksPurchaseOrderGet.js +33 -0
  111. package/api/threeclicks/threeclicksPurchaseOrderQuantitiesGet.js +33 -0
  112. package/api/threeclicks/threeclicksPurchaseOrderQuantityCreate.js +34 -0
  113. package/api/threeclicks/threeclicksPurchaseOrderQuantityCreateBulk.js +34 -0
  114. package/api/threeclicks/threeclicksPurchaseOrderQuantityUpdate.js +46 -0
  115. package/api/threeclicks/threeclicksPurchaseOrderQuantityUpdateBulk.js +46 -0
  116. package/api/threeclicks/threeclicksPurchaseOrderUpdate.js +35 -0
  117. package/api/threeclicks/threeclicksPurchaseOrdersGet.js +30 -0
  118. package/api/threeclicks/threeclicksShipmentCreate.js +34 -0
  119. package/api/threeclicks/threeclicksShipmentGet.js +33 -0
  120. package/api/threeclicks/threeclicksShipmentOrderAdd.js +38 -0
  121. package/api/threeclicks/threeclicksShipmentOrderQuantityCreate.js +35 -0
  122. package/api/threeclicks/threeclicksShipmentQuantityUpdate.js +35 -0
  123. package/api/threeclicks/threeclicksShipmentStatusUpdate.js +45 -0
  124. package/api/threeclicks/threeclicksShipmentsGet.js +52 -0
  125. package/api/threeclicks/threeclicksSignatureCalculate.js +73 -0
  126. package/api/threeclicks/threeclicksStyleCodesGet.js +46 -0
  127. package/api/threeclicks/threeclicksStyleColourCreate.js +47 -0
  128. package/api/threeclicks/threeclicksStyleColourDelete.js +34 -0
  129. package/api/threeclicks/threeclicksStyleCreate.js +44 -0
  130. package/api/threeclicks/threeclicksStyleDelete.js +33 -0
  131. package/api/threeclicks/threeclicksStyleGet.js +43 -0
  132. package/api/threeclicks/threeclicksStylePriceUpdate.js +70 -0
  133. package/api/threeclicks/threeclicksStyleUpdate.js +47 -0
  134. package/api/threeclicks/threeclicksStylesGet.js +45 -0
  135. package/api/utils.js +85 -117
  136. package/api/validators.js +1 -1
  137. package/api/workable/workable.utils.js +8 -13
  138. package/api/workable/workableGet.js +16 -11
  139. package/api/workable/workableJobCandidateCreate.js +10 -10
  140. package/api/workable/workableJobGet.js +5 -5
  141. package/api/workable/workableJobMembersGet.js +5 -5
  142. package/api/workable/workableJobStagesGet.js +5 -5
  143. package/api/yotpo/yotpo.utils.js +17 -22
  144. package/api/yotpo/yotpoCampaignsGet.js +6 -6
  145. package/api/yotpo/yotpoCustomerActionRecord.js +7 -7
  146. package/api/yotpo/yotpoCustomerAnniversaryGet.js +7 -7
  147. package/api/yotpo/yotpoCustomerAnniversarySet.js +7 -7
  148. package/api/yotpo/yotpoCustomerBirthdaySet.js +7 -7
  149. package/api/yotpo/yotpoCustomerGet.js +6 -6
  150. package/api/yotpo/yotpoCustomerPointsAdjust.js +7 -7
  151. package/api/yotpo/yotpoCustomerUpsert.js +7 -7
  152. package/api/yotpo/yotpoCustomersRecentGet.js +6 -6
  153. package/api/yotpo/yotpoRedemptionCreate.js +7 -7
  154. package/api/yotpo/yotpoRedemptionOptionsGet.js +6 -6
  155. package/api/yotpo/yotpoVipTiersGet.js +5 -5
  156. package/api/youtube/youtube.constants.js +5 -0
  157. package/api/youtube/youtube.utils.js +77 -0
  158. package/api/youtube/youtubeChannelVideosGet.js +25 -70
  159. package/package.json +1 -1
@@ -1,3 +1,3 @@
1
1
  module.exports = {
2
2
  MAX_REQUEST_ITEMS: 100,
3
- };
3
+ };
@@ -48,40 +48,7 @@ const setSessionId = async (credsPayload, sessionId) => {
48
48
  SESSION_IDS.set(CLIENT_ID, sessionId);
49
49
  };
50
50
 
51
- const withPeoplevoxAuth = async (fetchPayload, next) => {
52
- const { context = {} } = fetchPayload;
53
- const { credsPayload } = context;
54
-
55
- const sessionIdResponse = await getSessionId(credsPayload);
56
-
57
- const {
58
- ok: sessionIdOk,
59
- data: sessionId,
60
- } = sessionIdResponse;
61
-
62
- if (!sessionIdOk) {
63
- return sessionIdResponse;
64
- }
65
-
66
- const response = await next({
67
- ...fetchPayload,
68
- context: {
69
- ...context,
70
- sessionId,
71
- },
72
- });
73
-
74
- if (response.ok) {
75
- await setSessionId(credsPayload, sessionId);
76
- return response;
77
- }
78
-
79
- // TODO: Check if it was an auth error, and try another auth method if so
80
- return response;
81
- };
82
-
83
51
  module.exports = {
84
52
  getSessionId,
85
53
  setSessionId,
86
- withPeoplevoxAuth,
87
54
  };
@@ -1,7 +1,12 @@
1
1
  const csvtojson = require('csvtojson');
2
2
  const xml2js = require('xml2js');
3
- const { FetchClient, credsFromPayload, appendUrlToBase, logDeep, Chain } = require('../utils');
4
- const { withPeoplevoxAuth } = require('../peoplevox/peoplevox.sessions');
3
+ const { resolveCreds } = require('../pipelineSteps');
4
+ const {
5
+ FetchClient,
6
+ appendUrlToBase,
7
+ logDeep,
8
+ } = require('../utils');
9
+ const { getSessionId, setSessionId } = require('../peoplevox/peoplevox.sessions');
5
10
 
6
11
  const xml2jsBuilder = new xml2js.Builder({
7
12
  headless: true,
@@ -45,17 +50,17 @@ const buildSoapEnvelope = ({
45
50
  return xml2jsBuilder.buildObject(envelopeObject);
46
51
  };
47
52
 
48
- // TODO: Split into multiple steps, allow mutating context
49
- const peoplevoxRequestPreparer = async (requestPayload, context) => {
50
- const { headers, body } = requestPayload;
51
- const { credsPayload, action, sessionId } = context;
52
- const { CLIENT_ID } = await credsFromPayload(credsPayload);
53
+ const useSoapEnvelope = async (state) => {
54
+ const { requestPayload, context } = state;
55
+ const { creds, action, sessionId } = context;
56
+ const { CLIENT_ID } = creds;
53
57
 
54
58
  if (!sessionId) {
55
59
  throw new Error('PeopleVox sessionId is required');
56
60
  }
57
61
 
58
62
  const baseUrl = `https://ap.peoplevox.net/${ CLIENT_ID }/Resources/IntegrationServicev4.asmx`;
63
+ const { headers, body } = requestPayload;
59
64
 
60
65
  const envelopeXml = buildSoapEnvelope({
61
66
  action,
@@ -67,14 +72,16 @@ const peoplevoxRequestPreparer = async (requestPayload, context) => {
67
72
  logDeep({ envelopeXml });
68
73
 
69
74
  return {
70
- ...requestPayload,
71
- url: appendUrlToBase(baseUrl, requestPayload.url),
72
- headers: {
73
- ...headers,
74
- 'Content-Type': 'text/xml; charset=utf-8',
75
- 'SOAPAction': `http://www.peoplevox.net/${ action }`,
75
+ requestPayload: {
76
+ ...requestPayload,
77
+ url: appendUrlToBase(baseUrl, requestPayload.url),
78
+ headers: {
79
+ ...headers,
80
+ 'Content-Type': 'text/xml; charset=utf-8',
81
+ 'SOAPAction': `http://www.peoplevox.net/${ action }`,
82
+ },
83
+ body: envelopeXml,
76
84
  },
77
- body: envelopeXml,
78
85
  };
79
86
  };
80
87
 
@@ -167,17 +174,54 @@ const hoistDetail = (state) => {
167
174
  };
168
175
 
169
176
  const peoplevoxClient = new FetchClient({
170
- requestPreparer: peoplevoxRequestPreparer,
171
- responseInterpreter: new Chain([
177
+ pipeline: [
178
+ resolveCreds,
179
+ useSoapEnvelope,
180
+ 'fetch',
172
181
  stripEnvelope,
173
182
  tryToParseDetailAsCsv,
174
183
  unwrapSingleDetail,
175
184
  hoistDetail,
176
- ]),
185
+ ],
177
186
  });
178
187
 
179
- peoplevoxClient.use(withPeoplevoxAuth);
188
+ const fetchWithoutAuth = peoplevoxClient.fetch.bind(peoplevoxClient);
189
+
190
+ peoplevoxClient.fetch = async ({
191
+ requestPayload,
192
+ context = {},
193
+ inspect = false,
194
+ }) => {
195
+ const { credsPayload } = context;
196
+
197
+ const sessionIdResponse = await getSessionId(credsPayload);
198
+
199
+ const {
200
+ ok: sessionIdOk,
201
+ data: sessionId,
202
+ } = sessionIdResponse;
203
+
204
+ if (!sessionIdOk) {
205
+ return sessionIdResponse;
206
+ }
207
+
208
+ const response = await fetchWithoutAuth({
209
+ requestPayload,
210
+ context: {
211
+ ...context,
212
+ sessionId,
213
+ },
214
+ inspect,
215
+ });
216
+
217
+ if (response.ok) {
218
+ await setSessionId(credsPayload, sessionId);
219
+ }
220
+
221
+ // TODO: Check if it was an auth error, and try another auth method if so
222
+ return response;
223
+ };
180
224
 
181
225
  module.exports = {
182
226
  peoplevoxClient,
183
- };
227
+ };
@@ -32,11 +32,13 @@ const peoplevoxGetSingle = async (
32
32
  const resolvedSearchClause = searchClause || `${ idName }.Equals("${ id }")`;
33
33
 
34
34
  const response = await peoplevoxClient.fetch({
35
- method: 'post',
36
- body: {
37
- getRequest: {
38
- TemplateName: templateName,
39
- SearchClause: resolvedSearchClause,
35
+ requestPayload: {
36
+ method: 'post',
37
+ body: {
38
+ getRequest: {
39
+ TemplateName: templateName,
40
+ SearchClause: resolvedSearchClause,
41
+ },
40
42
  },
41
43
  },
42
44
  context: {
@@ -37,11 +37,13 @@ const peoplevoxOrderEdit = async (
37
37
  }
38
38
 
39
39
  return peoplevoxClient.fetch({
40
- method: 'post',
41
- body: {
42
- saveRequest: {
43
- TemplateName: 'Sales orders',
44
- CsvData: csvData,
40
+ requestPayload: {
41
+ method: 'post',
42
+ body: {
43
+ saveRequest: {
44
+ TemplateName: 'Sales orders',
45
+ CsvData: csvData,
46
+ },
45
47
  },
46
48
  },
47
49
  context: {
@@ -25,15 +25,17 @@ const peoplevoxReportGet = async (
25
25
  }
26
26
 
27
27
  const reportGetResponse = await peoplevoxClient.fetch({
28
- method: 'post',
29
- body: {
30
- getReportRequest: {
31
- TemplateName: reportName,
32
- ...(searchClause ? { SearchClause: searchClause } : {}),
33
- ...(perPage ? { ItemsPerPage: perPage } : {}),
34
- ...(filter ? { FilterClause: filter } : {}),
35
- ...(orderBy ? { OrderBy: orderBy } : {}),
36
- ...(columns ? { Columns: columns.join(',') } : {}),
28
+ requestPayload: {
29
+ method: 'post',
30
+ body: {
31
+ getReportRequest: {
32
+ TemplateName: reportName,
33
+ ...(searchClause ? { SearchClause: searchClause } : {}),
34
+ ...(perPage ? { ItemsPerPage: perPage } : {}),
35
+ ...(filter ? { FilterClause: filter } : {}),
36
+ ...(orderBy ? { OrderBy: orderBy } : {}),
37
+ ...(columns ? { Columns: columns.join(',') } : {}),
38
+ },
37
39
  },
38
40
  },
39
41
  context: {
@@ -0,0 +1,49 @@
1
+ const { appendUrlToBase, credsFromPayload } = require('./utils');
2
+
3
+ const resolveCreds = async (state) => {
4
+ const { context } = state;
5
+ const { creds, credsPayload } = context;
6
+
7
+ if (creds || !credsPayload) {
8
+ return {};
9
+ }
10
+
11
+ const resolvedCreds = await credsFromPayload(credsPayload);
12
+
13
+ return {
14
+ context: {
15
+ ...context,
16
+ creds: resolvedCreds,
17
+ },
18
+ };
19
+ };
20
+
21
+ const useBaseUrl = (baseUrl) => async (state) => {
22
+ const { requestPayload, context } = state;
23
+ const resolvedBaseUrl = baseUrl ?? context.baseUrl;
24
+
25
+ if (!resolvedBaseUrl) {
26
+ return {
27
+ breakChain: true,
28
+ response: {
29
+ ok: false,
30
+ error: {
31
+ code: 'NO_BASE_URL',
32
+ message: 'No base URL provided',
33
+ },
34
+ },
35
+ };
36
+ }
37
+
38
+ return {
39
+ requestPayload: {
40
+ ...requestPayload,
41
+ url: appendUrlToBase(resolvedBaseUrl, requestPayload.url),
42
+ },
43
+ };
44
+ };
45
+
46
+ module.exports = {
47
+ resolveCreds,
48
+ useBaseUrl,
49
+ };
@@ -1,11 +1,13 @@
1
1
  const { DEFAULT_API_VERSION } = require('../shopify/shopify.constants');
2
+ const { resolveCreds } = require('../pipelineSteps');
2
3
  const {
3
4
  FetchClient,
4
- Chain,
5
5
  appendUrlToBase,
6
6
  fetchClientCommonSteps,
7
+ jsonlToObjectArray,
7
8
  pathAsArray,
8
9
  objectDigNodeAtPath,
10
+ sentenceCaseString,
9
11
  } = require('../utils');
10
12
 
11
13
  const handleMutationUserErrors = async (state) => {
@@ -49,11 +51,11 @@ const handleMutationUserErrors = async (state) => {
49
51
  };
50
52
  };
51
53
 
52
- const addUrlAndAuthHeaders = async (state) => {
54
+ const useUrlAndAuthHeaders = async (state) => {
53
55
  const { requestPayload, context } = state;
54
56
  const {
55
- creds,
56
57
  apiVersion = DEFAULT_API_VERSION,
58
+ creds,
57
59
  } = context;
58
60
  const {
59
61
  STORE_HANDLE,
@@ -61,15 +63,13 @@ const addUrlAndAuthHeaders = async (state) => {
61
63
  } = creds;
62
64
 
63
65
  const baseUrl = `https://${ STORE_HANDLE }.myshopify.com/admin/api/${ apiVersion }/graphql.json`;
64
- const baseHeaders = {
65
- 'X-Shopify-Access-Token': API_KEY,
66
- };
67
66
 
68
67
  return {
69
68
  requestPayload: {
69
+ ...requestPayload,
70
70
  url: appendUrlToBase(baseUrl, requestPayload.url),
71
71
  headers: {
72
- ...baseHeaders,
72
+ 'X-Shopify-Access-Token': API_KEY,
73
73
  ...requestPayload.headers,
74
74
  },
75
75
  },
@@ -99,25 +99,96 @@ const movePageInfoToMeta = async (state) => {
99
99
  };
100
100
  };
101
101
 
102
- const shopifyClientRequestPreparer = new Chain([
103
- addUrlAndAuthHeaders,
104
- ]);
102
+ // https://shopify.dev/docs/api/usage/bulk-operations/queries
103
+ const parseShopifyJsonl = (jsonl) => {
104
+ const objects = jsonlToObjectArray(jsonl);
105
+ const objectsMap = new Map();
106
+ const objectsWithoutStitching = [];
107
+
108
+ for (const object of objects) {
109
+ const {
110
+ id: gid,
111
+ __parentId: parentGid,
112
+ } = object;
113
+
114
+ if (!gid) {
115
+ objectsWithoutStitching.push(object);
116
+ continue;
117
+ }
118
+
119
+ const [objectType] = gid.split('gid://shopify/')[1].split(/[^a-zA-Z0-9]+/);
120
+
121
+ objectsMap.set(gid, {
122
+ ...object,
123
+ selfType: objectType,
124
+ ...parentGid && { parentGid },
125
+ });
126
+ }
127
+
128
+ const objectTypeToProperty = (objectType) => `${ sentenceCaseString(objectType) }s`;
129
+
130
+ for (const [gid, object] of objectsMap) {
131
+ const {
132
+ selfType,
133
+ parentGid,
134
+ } = object;
135
+
136
+ if (!parentGid) {
137
+ continue;
138
+ }
139
+
140
+ const objectProperty = objectTypeToProperty(selfType);
141
+
142
+ let parentObject = objectsMap.get(parentGid);
143
+ if (!parentObject) {
144
+ continue;
145
+ }
105
146
 
106
- const shopifyClientResponseInterpreter = new Chain([
107
- movePageInfoToMeta,
108
- fetchClientCommonSteps.stripEdgesAndNodes,
109
- fetchClientCommonSteps.collapseDataWithOneValue,
110
- fetchClientCommonSteps.exitEarlyOnNotOk,
111
- fetchClientCommonSteps.exitEarlyOnGraphqlErrors,
112
- fetchClientCommonSteps.digToPath,
113
- handleMutationUserErrors,
114
- ]);
147
+ const nestedParent = gid.split('?')?.[1];
148
+
149
+ if (nestedParent) {
150
+ let [nestedParentType] = nestedParent.split('=');
151
+ nestedParentType = nestedParentType
152
+ .replaceAll('_id', '')
153
+ .split('_')
154
+ .map((word) => word[0].toUpperCase() + word.slice(1))
155
+ .join('');
156
+ nestedParentType = sentenceCaseString(nestedParentType);
157
+ parentObject = parentObject[nestedParentType];
158
+ }
159
+
160
+ if (!parentObject) {
161
+ continue;
162
+ }
163
+
164
+ parentObject[objectProperty] = parentObject[objectProperty] || [];
165
+ parentObject[objectProperty].push(objectsMap.get(gid));
166
+ }
167
+
168
+ const topLevelObjects = Array.from(objectsMap.values()).filter((object) => !object?.parentGid);
169
+
170
+ return [
171
+ ...topLevelObjects,
172
+ ...objectsWithoutStitching,
173
+ ];
174
+ };
115
175
 
116
176
  const shopifyClient = new FetchClient({
117
- requestPreparer: shopifyClientRequestPreparer,
118
- responseInterpreter: shopifyClientResponseInterpreter,
177
+ pipeline: [
178
+ resolveCreds,
179
+ useUrlAndAuthHeaders,
180
+ 'fetch',
181
+ movePageInfoToMeta,
182
+ fetchClientCommonSteps.stripEdgesAndNodes,
183
+ fetchClientCommonSteps.collapseDataWithOneValue,
184
+ fetchClientCommonSteps.exitEarlyOnNotOk,
185
+ fetchClientCommonSteps.exitEarlyOnGraphqlErrors,
186
+ fetchClientCommonSteps.digToPath,
187
+ handleMutationUserErrors,
188
+ ],
119
189
  });
120
190
 
121
191
  module.exports = {
192
+ parseShopifyJsonl,
122
193
  shopifyClient,
123
- };
194
+ };
@@ -0,0 +1,211 @@
1
+ const { randomUUID } = require('crypto');
2
+ const { createReadStream } = require('fs');
3
+ const fs = require('fs').promises;
4
+
5
+ const { HOSTED, TEMP_DIR } = require('../constants');
6
+ const { credsValidator } = require('../validators');
7
+ const {
8
+ ArgsWarden,
9
+ customFetch,
10
+ gidToId,
11
+ objectToFormData,
12
+ objHasAny,
13
+ objectArrayToJsonl,
14
+ jsonlToObjectArray,
15
+ wait,
16
+ valueProvided,
17
+ } = require('../utils');
18
+ const { shopifyStagedUploadCreate } = require('./shopifyStagedUploadCreate');
19
+ const { shopifyBulkOperationRunMutation } = require('./shopifyBulkOperationRunMutation');
20
+ const { shopifyBulkOperationGet } = require('./shopifyBulkOperationGet');
21
+
22
+ const bulkOpAttrs = `
23
+ id
24
+ status
25
+ type
26
+ objectCount
27
+ url
28
+ errorCode
29
+ `;
30
+
31
+ const createOrResumeBulkOpPayloadValidator = (payload) => {
32
+ const { mutation, input, bulkOperationId } = payload;
33
+
34
+ if (valueProvided(bulkOperationId)) {
35
+ return true;
36
+ }
37
+
38
+ return valueProvided(mutation) && objHasAny(input, ['data', 'filepath']);
39
+ };
40
+
41
+ const argsWarden = new ArgsWarden([
42
+ ['credsPayload', credsValidator],
43
+ ['createOrResumeBulkOpPayload', createOrResumeBulkOpPayloadValidator],
44
+ ]);
45
+
46
+ const shopifyBulkMutationDo = async (
47
+ credsPayload,
48
+ createOrResumeBulkOpPayload,
49
+ {
50
+ apiVersion,
51
+ clientIdentifier,
52
+ waitForResult = true,
53
+ } = {},
54
+ ) => {
55
+
56
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
57
+ credsPayload,
58
+ createOrResumeBulkOpPayload,
59
+ });
60
+ if (rejectResponse) {
61
+ return rejectResponse;
62
+ }
63
+
64
+ const {
65
+ mutation,
66
+ input,
67
+ bulkOperationId,
68
+ } = createOrResumeBulkOpPayload;
69
+
70
+ let bulkOperation;
71
+
72
+ if (!bulkOperationId) {
73
+
74
+ if (HOSTED) {
75
+ return {
76
+ ok: false,
77
+ error: 'This function is only available locally',
78
+ };
79
+ }
80
+
81
+ let {
82
+ data,
83
+ filepath,
84
+ } = input;
85
+
86
+ if (!filepath) {
87
+ await fs.mkdir(TEMP_DIR, { recursive: true });
88
+ filepath = `${ TEMP_DIR }/shopifyBulkMutationDo_${ randomUUID() }.jsonl`;
89
+ await fs.writeFile(filepath, objectArrayToJsonl(data));
90
+ }
91
+
92
+ const stagedUploadResponse = await shopifyStagedUploadCreate(
93
+ credsPayload,
94
+ {
95
+ resource: 'BULK_MUTATION_VARIABLES',
96
+ filename: filepath.split('/').pop(),
97
+ mimeType: 'text/jsonl',
98
+ httpMethod: 'POST',
99
+ },
100
+ { apiVersion },
101
+ );
102
+ if (!stagedUploadResponse.ok) {
103
+ return stagedUploadResponse;
104
+ }
105
+
106
+ const { url, parameters } = stagedUploadResponse.data.stagedTargets[0];
107
+
108
+ const formData = objectToFormData(
109
+ Object.fromEntries(parameters.map(({ name, value }) => [name, value])),
110
+ );
111
+ formData.append('file', createReadStream(filepath));
112
+
113
+ const uploadResponse = await customFetch(url, {
114
+ method: 'post',
115
+ body: formData,
116
+ });
117
+ if (!uploadResponse.ok) {
118
+ return uploadResponse;
119
+ }
120
+
121
+ const stagedUploadPath = uploadResponse.data?.PostResponse?.Key;
122
+
123
+ const mutationRunResponse = await shopifyBulkOperationRunMutation(
124
+ credsPayload,
125
+ mutation,
126
+ stagedUploadPath,
127
+ {
128
+ apiVersion,
129
+ clientIdentifier,
130
+ returnAttrs: bulkOpAttrs,
131
+ },
132
+ );
133
+
134
+ if (!waitForResult) {
135
+ return mutationRunResponse;
136
+ }
137
+
138
+ const { ok: mutationRunOk, data: mutationRunData } = mutationRunResponse;
139
+ if (!mutationRunOk) {
140
+ return mutationRunResponse;
141
+ }
142
+
143
+ bulkOperation = mutationRunData.bulkOperation;
144
+ bulkOperationId = gidToId(bulkOperation.id);
145
+ }
146
+
147
+ while (['CREATED', 'RUNNING'].includes(bulkOperation?.status)) {
148
+
149
+ if (bulkOperation) {
150
+ await wait(5000);
151
+ }
152
+
153
+ const operationResponse = await shopifyBulkOperationGet(
154
+ credsPayload,
155
+ bulkOperationId,
156
+ {
157
+ apiVersion,
158
+ attrs: bulkOpAttrs,
159
+ },
160
+ );
161
+ if (!operationResponse.ok) {
162
+ return operationResponse;
163
+ }
164
+
165
+ bulkOperation = operationResponse.data;
166
+ }
167
+
168
+ if (bulkOperation.status !== 'COMPLETED') {
169
+ return {
170
+ ok: false,
171
+ error: {
172
+ code: 'BULK_OPERATION_FAILED',
173
+ message: `Bulk operation failed with status ${ bulkOperation.status }`,
174
+ details: bulkOperation,
175
+ },
176
+ };
177
+ }
178
+
179
+ const resultsResponse = await customFetch(bulkOperation.url);
180
+ if (!resultsResponse.ok) {
181
+ return resultsResponse;
182
+ }
183
+
184
+ const results = jsonlToObjectArray(resultsResponse.data);
185
+
186
+ return {
187
+ ok: true,
188
+ data: results,
189
+ meta: {
190
+ bulkOperation,
191
+ },
192
+ };
193
+ };
194
+
195
+ const funcApiConfig = {
196
+ argsWarden,
197
+ };
198
+
199
+ module.exports = {
200
+ shopifyBulkMutationDo,
201
+ funcApiConfig,
202
+ };
203
+
204
+ /*
205
+ curl -X POST "http://localhost:8000/shopifyBulkMutationDo" \
206
+ -H "Content-Type: application/json" \
207
+ -d '{
208
+ "credsPayload": { "credsPath": "shopify.au" },
209
+ "arg": "1234"
210
+ }'
211
+ */