@foxtware/mineral 0.1.10 → 0.1.12
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.
|
@@ -4,8 +4,10 @@ const {
|
|
|
4
4
|
FetchClient,
|
|
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) => {
|
|
@@ -97,6 +99,80 @@ const movePageInfoToMeta = async (state) => {
|
|
|
97
99
|
};
|
|
98
100
|
};
|
|
99
101
|
|
|
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
|
+
}
|
|
146
|
+
|
|
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
|
+
};
|
|
175
|
+
|
|
100
176
|
const shopifyClient = new FetchClient({
|
|
101
177
|
pipeline: [
|
|
102
178
|
resolveCreds,
|
|
@@ -113,5 +189,6 @@ const shopifyClient = new FetchClient({
|
|
|
113
189
|
});
|
|
114
190
|
|
|
115
191
|
module.exports = {
|
|
192
|
+
parseShopifyJsonl,
|
|
116
193
|
shopifyClient,
|
|
117
194
|
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// https://shopify.dev/docs/api/admin-graphql/latest/mutations/bulkoperationrunquery
|
|
2
|
+
|
|
3
|
+
const { credsValidator } = require('../validators');
|
|
4
|
+
const { ArgsWarden } = require('../utils');
|
|
5
|
+
const { shopifyMutationDo } = require('./shopifyMutationDo');
|
|
6
|
+
|
|
7
|
+
const defaultReturnAttrs = `
|
|
8
|
+
id
|
|
9
|
+
type
|
|
10
|
+
status
|
|
11
|
+
objectCount
|
|
12
|
+
url
|
|
13
|
+
errorCode
|
|
14
|
+
`.trim();
|
|
15
|
+
|
|
16
|
+
const argsWarden = new ArgsWarden([
|
|
17
|
+
['credsPayload', credsValidator],
|
|
18
|
+
['query'],
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const shopifyBulkOperationRunQuery = async (
|
|
22
|
+
credsPayload,
|
|
23
|
+
query,
|
|
24
|
+
{
|
|
25
|
+
apiVersion,
|
|
26
|
+
returnAttrs = defaultReturnAttrs,
|
|
27
|
+
} = {},
|
|
28
|
+
) => {
|
|
29
|
+
|
|
30
|
+
const rejectResponse = await argsWarden.responseIfRejectingArgs({
|
|
31
|
+
credsPayload,
|
|
32
|
+
query,
|
|
33
|
+
});
|
|
34
|
+
if (rejectResponse) {
|
|
35
|
+
return rejectResponse;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return shopifyMutationDo(
|
|
39
|
+
credsPayload,
|
|
40
|
+
'bulkOperationRunQuery',
|
|
41
|
+
{
|
|
42
|
+
mutationVariables: {
|
|
43
|
+
query: {
|
|
44
|
+
type: 'String!',
|
|
45
|
+
value: query,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
returnSchema: `bulkOperation { ${ returnAttrs } }`,
|
|
49
|
+
apiVersion,
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const funcApiConfig = {
|
|
55
|
+
argsWarden,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
shopifyBulkOperationRunQuery,
|
|
60
|
+
funcApiConfig,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
curl -X POST "http://localhost:8000/shopifyBulkOperationRunQuery" \
|
|
65
|
+
-H "Content-Type: application/json" \
|
|
66
|
+
-d '{
|
|
67
|
+
"credsPayload": { "credsPath": "shopify.au" },
|
|
68
|
+
"query": "{ products { edges { node { id title handle } } } }"
|
|
69
|
+
}'
|
|
70
|
+
*/
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const { credsValidator } = require('../validators');
|
|
2
|
+
const {
|
|
3
|
+
ArgsWarden,
|
|
4
|
+
customFetch,
|
|
5
|
+
gidToId,
|
|
6
|
+
wait,
|
|
7
|
+
} = require('../utils');
|
|
8
|
+
const { parseShopifyJsonl } = require('./shopify.utils');
|
|
9
|
+
const { shopifyBulkOperationGet } = require('./shopifyBulkOperationGet');
|
|
10
|
+
const { shopifyBulkOperationRunQuery } = require('./shopifyBulkOperationRunQuery');
|
|
11
|
+
|
|
12
|
+
const bulkOpAttrs = `
|
|
13
|
+
id
|
|
14
|
+
status
|
|
15
|
+
type
|
|
16
|
+
objectCount
|
|
17
|
+
url
|
|
18
|
+
errorCode
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const argsWarden = new ArgsWarden([
|
|
22
|
+
['credsPayload', credsValidator],
|
|
23
|
+
['query'],
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const shopifyBulkQueryDo = async (
|
|
27
|
+
credsPayload,
|
|
28
|
+
query,
|
|
29
|
+
{
|
|
30
|
+
apiVersion,
|
|
31
|
+
waitForResult = true,
|
|
32
|
+
} = {},
|
|
33
|
+
) => {
|
|
34
|
+
|
|
35
|
+
const rejectResponse = await argsWarden.responseIfRejectingArgs({
|
|
36
|
+
credsPayload,
|
|
37
|
+
query,
|
|
38
|
+
});
|
|
39
|
+
if (rejectResponse) {
|
|
40
|
+
return rejectResponse;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const queryRunResponse = await shopifyBulkOperationRunQuery(
|
|
44
|
+
credsPayload,
|
|
45
|
+
query,
|
|
46
|
+
{
|
|
47
|
+
apiVersion,
|
|
48
|
+
returnAttrs: bulkOpAttrs,
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (!waitForResult) {
|
|
53
|
+
return queryRunResponse;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!queryRunResponse.ok) {
|
|
57
|
+
return queryRunResponse;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let bulkOperation = queryRunResponse.data.bulkOperation;
|
|
61
|
+
let bulkOperationId = gidToId(bulkOperation.id);
|
|
62
|
+
|
|
63
|
+
while (['CREATED', 'RUNNING'].includes(bulkOperation?.status)) {
|
|
64
|
+
|
|
65
|
+
if (bulkOperation) {
|
|
66
|
+
await wait(5000);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const operationResponse = await shopifyBulkOperationGet(
|
|
70
|
+
credsPayload,
|
|
71
|
+
bulkOperationId,
|
|
72
|
+
{
|
|
73
|
+
apiVersion,
|
|
74
|
+
attrs: bulkOpAttrs,
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
if (!operationResponse.ok) {
|
|
78
|
+
return operationResponse;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
bulkOperation = operationResponse.data;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (bulkOperation.status !== 'COMPLETED') {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
error: {
|
|
88
|
+
code: 'BULK_OPERATION_FAILED',
|
|
89
|
+
message: `Bulk operation failed with status ${ bulkOperation.status }`,
|
|
90
|
+
details: bulkOperation,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const resultsResponse = await customFetch(bulkOperation.url);
|
|
96
|
+
if (!resultsResponse.ok) {
|
|
97
|
+
return resultsResponse;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const results = parseShopifyJsonl(resultsResponse.data);
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
ok: true,
|
|
104
|
+
data: results,
|
|
105
|
+
meta: {
|
|
106
|
+
bulkOperation,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const funcApiConfig = {
|
|
112
|
+
argsWarden,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
shopifyBulkQueryDo,
|
|
117
|
+
funcApiConfig,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/*
|
|
121
|
+
curl -X POST "http://localhost:8000/shopifyBulkQueryDo" \
|
|
122
|
+
-H "Content-Type: application/json" \
|
|
123
|
+
-d '{
|
|
124
|
+
"credsPayload": { "credsPath": "shopify.au" },
|
|
125
|
+
"query": "{ products { edges { node { id title handle } } } }"
|
|
126
|
+
}'
|
|
127
|
+
*/
|
|
@@ -18,12 +18,6 @@ const tagsAddBulkMutation = `
|
|
|
18
18
|
}
|
|
19
19
|
`.trim();
|
|
20
20
|
|
|
21
|
-
const tagsAddBulkUserErrors = (bulkResult) => {
|
|
22
|
-
return bulkResult?.data?.tagsAdd?.userErrors
|
|
23
|
-
|| bulkResult?.tagsAdd?.userErrors
|
|
24
|
-
|| [];
|
|
25
|
-
};
|
|
26
|
-
|
|
27
21
|
const argsWarden = new ArgsWarden([
|
|
28
22
|
['credsPayload', credsValidator],
|
|
29
23
|
['gids', Array.isArray],
|
|
@@ -35,9 +29,7 @@ const shopifyTagsAddBulk = async (
|
|
|
35
29
|
gids,
|
|
36
30
|
tags,
|
|
37
31
|
{
|
|
38
|
-
|
|
39
|
-
clientIdentifier,
|
|
40
|
-
waitForResult = true,
|
|
32
|
+
...bulkMutationOptions
|
|
41
33
|
} = {},
|
|
42
34
|
) => {
|
|
43
35
|
|
|
@@ -61,11 +53,7 @@ const shopifyTagsAddBulk = async (
|
|
|
61
53
|
})),
|
|
62
54
|
},
|
|
63
55
|
},
|
|
64
|
-
|
|
65
|
-
apiVersion,
|
|
66
|
-
clientIdentifier,
|
|
67
|
-
waitForResult,
|
|
68
|
-
},
|
|
56
|
+
bulkMutationOptions,
|
|
69
57
|
);
|
|
70
58
|
};
|
|
71
59
|
|
|
@@ -75,7 +63,6 @@ const funcApiConfig = {
|
|
|
75
63
|
|
|
76
64
|
module.exports = {
|
|
77
65
|
shopifyTagsAddBulk,
|
|
78
|
-
tagsAddBulkUserErrors,
|
|
79
66
|
funcApiConfig,
|
|
80
67
|
};
|
|
81
68
|
|
|
@@ -84,7 +71,7 @@ curl -X POST "http://localhost:8000/shopifyTagsAddBulk" \
|
|
|
84
71
|
-H "Content-Type: application/json" \
|
|
85
72
|
-d '{
|
|
86
73
|
"credsPayload": { "credsPath": "shopify.au" },
|
|
87
|
-
"gids": ["gid://shopify/Customer/123"],
|
|
88
|
-
"tags": ["
|
|
74
|
+
"gids": ["gid://shopify/Customer/123", "gid://shopify/Customer/456"],
|
|
75
|
+
"tags": ["martian_sympathisers"]
|
|
89
76
|
}'
|
|
90
77
|
*/
|