@foxtware/mineral 0.1.17 → 0.1.18

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 (90) hide show
  1. package/.creds.yml.sample +16 -7
  2. package/AGENTS.md +3 -0
  3. package/_build_scripts/publish.js +15 -1
  4. package/api/backblaze/backblaze.constants.js +11 -0
  5. package/api/backblaze/backblaze.utils.js +322 -0
  6. package/api/backblaze/backblazeBucketsGet.js +68 -0
  7. package/api/backblaze/backblazeFileDelete.js +62 -0
  8. package/api/backblaze/backblazeFileHide.js +62 -0
  9. package/api/backblaze/backblazeFileUpload.js +142 -0
  10. package/api/backblaze/backblazeFilesGet.js +85 -0
  11. package/api/backblaze/backblazeGet.js +139 -0
  12. package/api/backblaze/docs.md +6 -0
  13. package/api/etsy/etsyListingUpdate.js +1 -1
  14. package/api/gorgias/gorgias.constants.js +8 -0
  15. package/api/gorgias/gorgias.utils.js +46 -0
  16. package/api/gorgias/gorgiasAccountGet.js +51 -0
  17. package/api/gorgias/gorgiasCustomerGet.js +70 -0
  18. package/api/gorgias/gorgiasCustomersGet.js +56 -0
  19. package/api/gorgias/gorgiasGet.js +150 -0
  20. package/api/gorgias/gorgiasGetSingle.js +26 -0
  21. package/api/gorgias/gorgiasMessagesGet.js +56 -0
  22. package/api/gorgias/gorgiasTagsGet.js +52 -0
  23. package/api/gorgias/gorgiasTicketCreate.js +66 -0
  24. package/api/gorgias/gorgiasTicketGet.js +70 -0
  25. package/api/gorgias/gorgiasTicketMessageCreate.js +65 -0
  26. package/api/gorgias/gorgiasTicketMessagesGet.js +66 -0
  27. package/api/gorgias/gorgiasTicketUpdate.js +62 -0
  28. package/api/gorgias/gorgiasTicketsGet.js +58 -0
  29. package/api/gorgias/gorgiasUsersGet.js +52 -0
  30. package/api/gorgias/gorgiasViewItemsGet.js +57 -0
  31. package/api/gorgias/gorgiasViewsGet.js +72 -0
  32. package/api/linear/docs.md +6 -0
  33. package/api/linear/linear.constants.js +3 -0
  34. package/api/linear/linear.utils.js +59 -0
  35. package/api/linear/linearAttachmentCreate.js +77 -0
  36. package/api/linear/linearAttachmentDelete.js +68 -0
  37. package/api/linear/linearCommentCreate.js +77 -0
  38. package/api/linear/linearCommentDelete.js +68 -0
  39. package/api/linear/linearCommentGet.js +77 -0
  40. package/api/linear/linearCommentUpdate.js +87 -0
  41. package/api/linear/linearCommentsGet.js +70 -0
  42. package/api/linear/linearCyclesGet.js +67 -0
  43. package/api/linear/linearGet.js +218 -0
  44. package/api/linear/linearIssueAddLabel.js +77 -0
  45. package/api/linear/linearIssueArchive.js +74 -0
  46. package/api/linear/linearIssueCreate.js +94 -0
  47. package/api/linear/linearIssueDelete.js +74 -0
  48. package/api/linear/linearIssueGet.js +87 -0
  49. package/api/linear/linearIssueLabelCreate.js +81 -0
  50. package/api/linear/linearIssueLabelsGet.js +66 -0
  51. package/api/linear/linearIssueRemoveLabel.js +77 -0
  52. package/api/linear/linearIssueUpdate.js +96 -0
  53. package/api/linear/linearIssuesGet.js +39 -63
  54. package/api/linear/linearOrganizationGet.js +64 -0
  55. package/api/linear/linearProjectCreate.js +92 -0
  56. package/api/linear/linearProjectGet.js +74 -0
  57. package/api/linear/linearProjectUpdate.js +80 -0
  58. package/api/linear/linearProjectsGet.js +71 -0
  59. package/api/linear/linearTeamGet.js +71 -0
  60. package/api/linear/linearTeamsGet.js +66 -0
  61. package/api/linear/linearUserGet.js +72 -0
  62. package/api/linear/linearUsersGet.js +71 -0
  63. package/api/linear/linearViewerGet.js +65 -0
  64. package/api/linear/linearWebhookCreate.js +77 -0
  65. package/api/linear/linearWebhookDelete.js +68 -0
  66. package/api/linear/linearWebhooksGet.js +67 -0
  67. package/api/linear/linearWorkflowStatesGet.js +73 -0
  68. package/api/pipe17/pipe17.utils.js +7 -20
  69. package/api/pipe17/pipe17ArrivalGet.js +1 -1
  70. package/api/pipe17/pipe17FulfillmentGet.js +1 -1
  71. package/api/pipe17/pipe17InventoryItemGet.js +1 -1
  72. package/api/pipe17/pipe17JobGet.js +11 -24
  73. package/api/pipe17/pipe17LocationGet.js +1 -1
  74. package/api/pipe17/pipe17OrderGet.js +1 -1
  75. package/api/pipe17/pipe17ProductGet.js +1 -1
  76. package/api/pipe17/pipe17PurchaseGet.js +1 -1
  77. package/api/pipe17/pipe17ReceiptGet.js +1 -1
  78. package/api/pipe17/pipe17ReturnGet.js +1 -1
  79. package/api/shopify/shopifyFileCreate.js +107 -0
  80. package/api/shopify/shopifyMetafieldDefinitionCreate.js +2 -0
  81. package/api/shopify/shopifyMetafieldDefinitionGet.js +108 -0
  82. package/api/shopify/shopifyMetafieldDefinitionPropagate.js +27 -71
  83. package/api/shopify/shopifyThemeFileGet.js +112 -0
  84. package/api/shopify/shopifyThemeFilePropagate.js +410 -0
  85. package/api/shopify/shopifyThemeFilesUpsert.js +88 -0
  86. package/api/starshipit/starshipit.utils.js +1 -0
  87. package/api/starshipit/starshipitOrderGet.js +2 -10
  88. package/api/utils.js +12 -4
  89. package/package.json +1 -1
  90. package/server.utils.js +1 -1
@@ -0,0 +1,410 @@
1
+ // https://shopify.dev/docs/api/admin-graphql/latest/queries/theme
2
+ // https://shopify.dev/docs/api/admin-graphql/latest/mutations/themeFilesUpsert
3
+ // https://shopify.dev/docs/api/admin-graphql/latest/mutations/fileCreate
4
+
5
+ const { credsValidator } = require('../validators');
6
+ const { ArgsWarden, wait } = require('../utils');
7
+ const { shopifyThemeFileGet } = require('./shopifyThemeFileGet');
8
+ const { shopifyThemeFilesUpsert } = require('./shopifyThemeFilesUpsert');
9
+ const { shopifyFilesGet } = require('./shopifyFilesGet');
10
+ const { shopifyFileCreate } = require('./shopifyFileCreate');
11
+
12
+ const argsWarden = new ArgsWarden([
13
+ ['fromCredsPayload', credsValidator],
14
+ ['toCredsPayload', credsValidator],
15
+ ['fromThemeId'],
16
+ ['toThemeId'],
17
+ ['filepath'],
18
+ ]);
19
+
20
+ const fileLookupAttrs = `
21
+ id
22
+ alt
23
+ fileStatus
24
+ preview {
25
+ image {
26
+ url
27
+ }
28
+ }
29
+ ... on MediaImage {
30
+ image {
31
+ url
32
+ }
33
+ }
34
+ ... on GenericFile {
35
+ url
36
+ }
37
+ ... on Video {
38
+ originalSource {
39
+ url
40
+ }
41
+ }
42
+ `.trim();
43
+
44
+ const ASSET_LINK_REGEX = /shopify:\/\/shop_(?:images|videos|files)\/[^"'\\\s]+|https?:\/\/cdn\.shopify\.com\/[^"'\\\s]+/g;
45
+
46
+ const themeFileTextContent = (themeFile) => {
47
+ const body = themeFile?.body;
48
+ if (!body) {
49
+ return null;
50
+ }
51
+ if (body.content != null) {
52
+ return body.content;
53
+ }
54
+ if (body.contentBase64 != null) {
55
+ return Buffer.from(body.contentBase64, 'base64').toString('utf8');
56
+ }
57
+ return null;
58
+ };
59
+
60
+ const filenameFromAssetLink = (assetLink) => {
61
+ const withoutQuery = assetLink.split('?')[0];
62
+ return decodeURIComponent(withoutQuery.split('/').pop());
63
+ };
64
+
65
+ const schemeFromAssetLink = (assetLink) => {
66
+ const shopifySchemeMatch = assetLink.match(/^shopify:\/\/(shop_(?:images|videos|files))\//);
67
+ if (shopifySchemeMatch) {
68
+ return shopifySchemeMatch[1];
69
+ }
70
+
71
+ const filename = filenameFromAssetLink(assetLink).toLowerCase();
72
+ if (/\.(mp4|mov|webm|m4v)$/.test(filename)) {
73
+ return 'shop_videos';
74
+ }
75
+ if (/\.(pdf|txt|csv|json|zip|xml)$/.test(filename)) {
76
+ return 'shop_files';
77
+ }
78
+ return 'shop_images';
79
+ };
80
+
81
+ const shopifyAssetUrl = (scheme, filename) => `shopify://${ scheme }/${ filename }`;
82
+
83
+ const fileSourceUrl = (file) => (
84
+ file?.image?.url
85
+ || file?.url
86
+ || file?.originalSource?.url
87
+ || file?.preview?.image?.url
88
+ || null
89
+ );
90
+
91
+ const destinationUrlForAsset = (assetLink, filename, file) => {
92
+ if (assetLink.startsWith('shopify://')) {
93
+ return shopifyAssetUrl(schemeFromAssetLink(assetLink), filename);
94
+ }
95
+
96
+ return fileSourceUrl(file) || shopifyAssetUrl(schemeFromAssetLink(assetLink), filename);
97
+ };
98
+
99
+ const findFileByFilename = async (
100
+ credsPayload,
101
+ filename,
102
+ {
103
+ apiVersion,
104
+ } = {},
105
+ ) => {
106
+ const response = await shopifyFilesGet(credsPayload, {
107
+ apiVersion,
108
+ attrs: fileLookupAttrs,
109
+ queries: [`filename:"${ filename }"`],
110
+ limit: 10,
111
+ });
112
+
113
+ if (!response.ok) {
114
+ return response;
115
+ }
116
+
117
+ const files = response.data || [];
118
+ const exactMatch = files.find((file) => {
119
+ const sourceUrl = fileSourceUrl(file);
120
+ if (!sourceUrl) {
121
+ return false;
122
+ }
123
+ return filenameFromAssetLink(sourceUrl) === filename;
124
+ });
125
+
126
+ return {
127
+ ok: true,
128
+ data: exactMatch || files[0] || null,
129
+ };
130
+ };
131
+
132
+ const waitForFileReady = async (
133
+ credsPayload,
134
+ filename,
135
+ {
136
+ apiVersion,
137
+ attempts = 8,
138
+ intervalMs = 1500,
139
+ } = {},
140
+ ) => {
141
+ let file = null;
142
+
143
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
144
+ if (attempt > 0) {
145
+ await wait(intervalMs);
146
+ }
147
+
148
+ const lookupResponse = await findFileByFilename(credsPayload, filename, { apiVersion });
149
+ if (!lookupResponse.ok) {
150
+ return lookupResponse;
151
+ }
152
+
153
+ file = lookupResponse.data;
154
+ if (file?.fileStatus === 'READY' && fileSourceUrl(file)) {
155
+ return {
156
+ ok: true,
157
+ data: file,
158
+ };
159
+ }
160
+
161
+ if (file?.fileStatus === 'FAILED') {
162
+ return {
163
+ ok: false,
164
+ error: {
165
+ code: 'FILE_PROCESSING_FAILED',
166
+ message: `Uploaded file "${ filename }" failed processing.`,
167
+ details: file,
168
+ },
169
+ };
170
+ }
171
+ }
172
+
173
+ return {
174
+ ok: true,
175
+ data: file,
176
+ };
177
+ };
178
+
179
+ const resolveAssetOnToStore = async (
180
+ fromCredsPayload,
181
+ toCredsPayload,
182
+ assetLink,
183
+ {
184
+ apiVersion,
185
+ } = {},
186
+ ) => {
187
+ const filename = filenameFromAssetLink(assetLink);
188
+
189
+ const toFileResponse = await findFileByFilename(toCredsPayload, filename, { apiVersion });
190
+ if (!toFileResponse.ok) {
191
+ return toFileResponse;
192
+ }
193
+
194
+ if (toFileResponse.data) {
195
+ return {
196
+ ok: true,
197
+ data: {
198
+ fromUrl: assetLink,
199
+ toUrl: destinationUrlForAsset(assetLink, filename, toFileResponse.data),
200
+ filename,
201
+ uploaded: false,
202
+ file: toFileResponse.data,
203
+ },
204
+ };
205
+ }
206
+
207
+ let originalSource = assetLink.startsWith('http') ? assetLink : null;
208
+
209
+ if (!originalSource) {
210
+ const fromFileResponse = await findFileByFilename(fromCredsPayload, filename, { apiVersion });
211
+ if (!fromFileResponse.ok) {
212
+ return fromFileResponse;
213
+ }
214
+ if (!fromFileResponse.data) {
215
+ return {
216
+ ok: false,
217
+ error: {
218
+ code: 'ASSET_NOT_FOUND',
219
+ message: `Asset "${ filename }" not found in Files on the from store.`,
220
+ details: { assetLink, filename },
221
+ },
222
+ };
223
+ }
224
+ originalSource = fileSourceUrl(fromFileResponse.data);
225
+ if (!originalSource) {
226
+ return {
227
+ ok: false,
228
+ error: {
229
+ code: 'ASSET_URL_MISSING',
230
+ message: `No downloadable URL for asset "${ filename }" on the from store.`,
231
+ details: { assetLink, filename, file: fromFileResponse.data },
232
+ },
233
+ };
234
+ }
235
+ }
236
+
237
+ const createResponse = await shopifyFileCreate(
238
+ toCredsPayload,
239
+ {
240
+ filename,
241
+ originalSource,
242
+ },
243
+ { apiVersion },
244
+ );
245
+
246
+ if (!createResponse.ok) {
247
+ return createResponse;
248
+ }
249
+
250
+ const readyResponse = await waitForFileReady(toCredsPayload, filename, { apiVersion });
251
+ if (!readyResponse.ok) {
252
+ return readyResponse;
253
+ }
254
+
255
+ const createdFile = readyResponse.data
256
+ || (
257
+ Array.isArray(createResponse.data?.files)
258
+ ? createResponse.data.files[0]
259
+ : createResponse.data?.files
260
+ );
261
+
262
+ return {
263
+ ok: true,
264
+ data: {
265
+ fromUrl: assetLink,
266
+ toUrl: destinationUrlForAsset(assetLink, filename, createdFile),
267
+ filename,
268
+ uploaded: true,
269
+ originalSource,
270
+ file: createdFile,
271
+ },
272
+ };
273
+ };
274
+
275
+ const shopifyThemeFilePropagate = async (
276
+ fromCredsPayload,
277
+ toCredsPayload,
278
+ fromThemeId,
279
+ toThemeId,
280
+ filepath,
281
+ {
282
+ apiVersion,
283
+ } = {},
284
+ ) => {
285
+
286
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
287
+ fromCredsPayload,
288
+ toCredsPayload,
289
+ fromThemeId,
290
+ toThemeId,
291
+ filepath,
292
+ });
293
+ if (rejectResponse) {
294
+ return rejectResponse;
295
+ }
296
+
297
+ const fromFileResponse = await shopifyThemeFileGet(
298
+ fromCredsPayload,
299
+ fromThemeId,
300
+ filepath,
301
+ { apiVersion },
302
+ );
303
+ if (!fromFileResponse.ok) {
304
+ return fromFileResponse;
305
+ }
306
+
307
+ const fromThemeFile = fromFileResponse.data.find((file) => file?.filename === filepath)
308
+ || fromFileResponse.data[0];
309
+
310
+ if (!fromThemeFile) {
311
+ return {
312
+ ok: false,
313
+ error: {
314
+ code: 'NOT_FOUND',
315
+ message: `Theme file "${ filepath }" not found on from theme.`,
316
+ details: { fromThemeId, filepath },
317
+ },
318
+ };
319
+ }
320
+
321
+ const content = themeFileTextContent(fromThemeFile);
322
+ if (content == null) {
323
+ return {
324
+ ok: false,
325
+ error: {
326
+ code: 'UNSUPPORTED_BODY',
327
+ message: `Theme file "${ filepath }" has no text body to propagate.`,
328
+ details: fromThemeFile,
329
+ },
330
+ };
331
+ }
332
+
333
+ const assetLinks = [...new Set(content.match(ASSET_LINK_REGEX) || [])];
334
+ const assetMappings = [];
335
+
336
+ for (const assetLink of assetLinks) {
337
+ const resolveResponse = await resolveAssetOnToStore(
338
+ fromCredsPayload,
339
+ toCredsPayload,
340
+ assetLink,
341
+ { apiVersion },
342
+ );
343
+ if (!resolveResponse.ok) {
344
+ return resolveResponse;
345
+ }
346
+ assetMappings.push(resolveResponse.data);
347
+ }
348
+
349
+ let translatedContent = content;
350
+ for (const { fromUrl, toUrl } of assetMappings) {
351
+ if (fromUrl === toUrl) {
352
+ continue;
353
+ }
354
+ translatedContent = translatedContent.split(fromUrl).join(toUrl);
355
+ }
356
+
357
+ const upsertResponse = await shopifyThemeFilesUpsert(
358
+ toCredsPayload,
359
+ toThemeId,
360
+ {
361
+ filename: filepath,
362
+ body: {
363
+ type: 'TEXT',
364
+ value: translatedContent,
365
+ },
366
+ },
367
+ { apiVersion },
368
+ );
369
+
370
+ if (!upsertResponse.ok) {
371
+ return upsertResponse;
372
+ }
373
+
374
+ return {
375
+ ok: true,
376
+ data: {
377
+ filepath,
378
+ fromThemeId,
379
+ toThemeId,
380
+ assetMappings: assetMappings.map(({ fromUrl, toUrl, filename, uploaded }) => ({
381
+ fromUrl,
382
+ toUrl,
383
+ filename,
384
+ uploaded,
385
+ })),
386
+ upsert: upsertResponse.data,
387
+ },
388
+ };
389
+ };
390
+
391
+ const funcApiConfig = {
392
+ argsWarden,
393
+ };
394
+
395
+ module.exports = {
396
+ shopifyThemeFilePropagate,
397
+ funcApiConfig,
398
+ };
399
+
400
+ /*
401
+ curl -X POST "http://localhost:8000/shopifyThemeFilePropagate" \
402
+ -H "Content-Type: application/json" \
403
+ -d '{
404
+ "fromCredsPayload": { "credsPath": "shopify.au" },
405
+ "toCredsPayload": { "credsPath": "shopify.us" },
406
+ "fromThemeId": "1234",
407
+ "toThemeId": "5678",
408
+ "filepath": "templates/page.sign_up_beauty.json"
409
+ }'
410
+ */
@@ -0,0 +1,88 @@
1
+ // https://shopify.dev/docs/api/admin-graphql/latest/mutations/themeFilesUpsert
2
+
3
+ const { credsValidator } = require('../validators');
4
+ const { ArgsWarden, ensureArray, everyIfArray } = require('../utils');
5
+ const { shopifyMutationDo } = require('./shopifyMutationDo');
6
+
7
+ const themeFileInputValidator = ({
8
+ filename,
9
+ body,
10
+ } = {}) => Boolean(filename && body?.type && body?.value != null);
11
+
12
+ const argsWarden = new ArgsWarden([
13
+ ['credsPayload', credsValidator],
14
+ ['themeId'],
15
+ ['files', (files) => everyIfArray(themeFileInputValidator, files)],
16
+ ]);
17
+
18
+ const shopifyThemeFilesUpsert = async (
19
+ credsPayload,
20
+ themeId,
21
+ files,
22
+ {
23
+ apiVersion,
24
+ returnSchema = `
25
+ upsertedThemeFiles {
26
+ filename
27
+ }
28
+ job {
29
+ id
30
+ done
31
+ }
32
+ `.trim(),
33
+ } = {},
34
+ ) => {
35
+
36
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
37
+ credsPayload,
38
+ themeId,
39
+ files,
40
+ });
41
+ if (rejectResponse) {
42
+ return rejectResponse;
43
+ }
44
+
45
+ return shopifyMutationDo(
46
+ credsPayload,
47
+ 'themeFilesUpsert',
48
+ {
49
+ mutationVariables: {
50
+ themeId: {
51
+ type: 'ID!',
52
+ value: `gid://shopify/OnlineStoreTheme/${ themeId }`,
53
+ },
54
+ files: {
55
+ type: '[OnlineStoreThemeFilesUpsertFileInput!]!',
56
+ value: ensureArray(files),
57
+ },
58
+ },
59
+ returnSchema,
60
+ apiVersion,
61
+ },
62
+ );
63
+ };
64
+
65
+ const funcApiConfig = {
66
+ argsWarden,
67
+ };
68
+
69
+ module.exports = {
70
+ shopifyThemeFilesUpsert,
71
+ funcApiConfig,
72
+ };
73
+
74
+ /*
75
+ curl -X POST "http://localhost:8000/shopifyThemeFilesUpsert" \
76
+ -H "Content-Type: application/json" \
77
+ -d '{
78
+ "credsPayload": { "credsPath": "shopify.au" },
79
+ "themeId": "129840808008",
80
+ "files": {
81
+ "filename": "templates/index.json",
82
+ "body": {
83
+ "type": "TEXT",
84
+ "value": "{ \"sections\": {}, \"order\": [] }"
85
+ }
86
+ }
87
+ }'
88
+ */
@@ -63,6 +63,7 @@ const starshipitClient = new FetchClient({
63
63
  'fetch',
64
64
  fetchClientCommonSteps.exitEarlyOnNotOk,
65
65
  interpretStarshipitResponse,
66
+ fetchClientCommonSteps.digToPath,
66
67
  ],
67
68
  });
68
69
 
@@ -32,7 +32,7 @@ const starshipitOrderGet = async (
32
32
  orderNumber,
33
33
  } = orderIdentifier;
34
34
 
35
- const response = await starshipitClient.fetch({
35
+ return starshipitClient.fetch({
36
36
  requestPayload: {
37
37
  url: '/orders',
38
38
  params: {
@@ -42,17 +42,9 @@ const starshipitOrderGet = async (
42
42
  },
43
43
  context: {
44
44
  credsPayload,
45
+ resultPath: 'order',
45
46
  },
46
47
  });
47
-
48
- if (!response?.ok) {
49
- return response;
50
- }
51
-
52
- return {
53
- ...response,
54
- data: response.data?.order ?? response.data,
55
- };
56
48
  };
57
49
 
58
50
  const funcApiConfig = {
package/api/utils.js CHANGED
@@ -22,6 +22,8 @@ const sentenceCaseString = (string) => `${ string[0].toLowerCase() }${ string.sl
22
22
 
23
23
  const normalise = (value) => (value || '').toString().trim().toLowerCase();
24
24
 
25
+ const valueProvided = (value) => value !== undefined && value !== null;
26
+
25
27
  const credsByPath = (credsPath, credsObject) => {
26
28
  const pathNodes = pathAsArray(credsPath);
27
29
 
@@ -166,6 +168,10 @@ const bodyPassesThrough = (body) => {
166
168
  return true;
167
169
  }
168
170
 
171
+ if (Buffer.isBuffer(body)) {
172
+ return true;
173
+ }
174
+
169
175
  return typeof body.getHeaders === 'function';
170
176
  };
171
177
 
@@ -394,6 +400,10 @@ const stripGraphqlEdgesAndNodes = (value) => {
394
400
  return value.edges.map((edge) => stripGraphqlEdgesAndNodes(edge?.node));
395
401
  }
396
402
 
403
+ if (Array.isArray(value.nodes)) {
404
+ return value.nodes.map(stripGraphqlEdgesAndNodes);
405
+ }
406
+
397
407
  const output = {};
398
408
  for (const [key, nestedValue] of Object.entries(value)) {
399
409
  output[key] = stripGraphqlEdgesAndNodes(nestedValue);
@@ -648,14 +658,14 @@ const fetchClientCommonSteps = {
648
658
  const { resultPath } = context;
649
659
  const { data } = response;
650
660
 
651
- if (!data || !resultPath) {
661
+ if (!valueProvided(data) || !valueProvided(resultPath)) {
652
662
  return {};
653
663
  }
654
664
 
655
665
  const resultPathNodes = pathAsArray(resultPath);
656
666
  const dataAtPath = objectDigNodeAtPath(data, resultPathNodes);
657
667
 
658
- if (!dataAtPath) {
668
+ if (dataAtPath === undefined) {
659
669
  return {
660
670
  response: {
661
671
  ok: false,
@@ -676,8 +686,6 @@ const fetchClientCommonSteps = {
676
686
  },
677
687
  };
678
688
 
679
- const valueProvided = (value) => value !== undefined && value !== null;
680
-
681
689
  const ensureArray = (value) => {
682
690
  if (Array.isArray(value)) {
683
691
  return value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxtware/mineral",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "bin": {
5
5
  "mineral": "bin/mineral.js"
6
6
  },
package/server.utils.js CHANGED
@@ -3,7 +3,7 @@ const { HOSTED } = require('./api/constants');
3
3
  const { StringDecoder } = require('string_decoder');
4
4
 
5
5
  const respondJson = (res, statusCode, payload) => {
6
- // !HOSTED && logDeep(payload);
6
+ !HOSTED && logDeep(payload);
7
7
  const body = JSON.stringify(payload);
8
8
  res.writeHead(statusCode, {
9
9
  'Content-Type': 'application/json',