@decocms/apps-shopify 7.2.0
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.
- package/package.json +48 -0
- package/src/actions/cart/addItems.ts +36 -0
- package/src/actions/cart/updateCoupons.ts +31 -0
- package/src/actions/cart/updateItems.ts +31 -0
- package/src/actions/user/signIn.ts +40 -0
- package/src/actions/user/signUp.ts +31 -0
- package/src/client.ts +58 -0
- package/src/index.ts +43 -0
- package/src/init.ts +39 -0
- package/src/loaders/ProductDetailsPage.ts +31 -0
- package/src/loaders/ProductList.ts +102 -0
- package/src/loaders/ProductListingPage.ts +172 -0
- package/src/loaders/RelatedProducts.ts +42 -0
- package/src/loaders/cart.ts +74 -0
- package/src/loaders/shop.ts +35 -0
- package/src/loaders/user.ts +39 -0
- package/src/mod.ts +67 -0
- package/src/registry.ts +9 -0
- package/src/utils/__tests__/graphqlOperationName.test.ts +80 -0
- package/src/utils/__tests__/instrumentedFetch.test.ts +84 -0
- package/src/utils/__tests__/operationRouter.test.ts +56 -0
- package/src/utils/__tests__/transform.test.ts +216 -0
- package/src/utils/admin/admin.ts +57 -0
- package/src/utils/admin/queries.ts +29 -0
- package/src/utils/cart.ts +32 -0
- package/src/utils/cookies.ts +82 -0
- package/src/utils/enums.ts +438 -0
- package/src/utils/graphql.ts +69 -0
- package/src/utils/graphqlOperationName.ts +67 -0
- package/src/utils/instrumentedFetch.ts +55 -0
- package/src/utils/operationRouter.ts +70 -0
- package/src/utils/storefront/queries.ts +525 -0
- package/src/utils/transform.ts +430 -0
- package/src/utils/types.ts +191 -0
- package/src/utils/user.ts +23 -0
- package/src/utils/utils.ts +159 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
export enum CurrencyCode {
|
|
2
|
+
AED = "AED",
|
|
3
|
+
AFN = "AFN",
|
|
4
|
+
ALL = "ALL",
|
|
5
|
+
AMD = "AMD",
|
|
6
|
+
ANG = "ANG",
|
|
7
|
+
AOA = "AOA",
|
|
8
|
+
ARS = "ARS",
|
|
9
|
+
AUD = "AUD",
|
|
10
|
+
AWG = "AWG",
|
|
11
|
+
AZN = "AZN",
|
|
12
|
+
BAM = "BAM",
|
|
13
|
+
BBD = "BBD",
|
|
14
|
+
BDT = "BDT",
|
|
15
|
+
BGN = "BGN",
|
|
16
|
+
BHD = "BHD",
|
|
17
|
+
BIF = "BIF",
|
|
18
|
+
BMD = "BMD",
|
|
19
|
+
BND = "BND",
|
|
20
|
+
BOB = "BOB",
|
|
21
|
+
BRL = "BRL",
|
|
22
|
+
BSD = "BSD",
|
|
23
|
+
BTN = "BTN",
|
|
24
|
+
BWP = "BWP",
|
|
25
|
+
BYN = "BYN",
|
|
26
|
+
BZD = "BZD",
|
|
27
|
+
CAD = "CAD",
|
|
28
|
+
CDF = "CDF",
|
|
29
|
+
CHF = "CHF",
|
|
30
|
+
CLP = "CLP",
|
|
31
|
+
CNY = "CNY",
|
|
32
|
+
COP = "COP",
|
|
33
|
+
CRC = "CRC",
|
|
34
|
+
CVE = "CVE",
|
|
35
|
+
CZK = "CZK",
|
|
36
|
+
DJF = "DJF",
|
|
37
|
+
DKK = "DKK",
|
|
38
|
+
DOP = "DOP",
|
|
39
|
+
DZD = "DZD",
|
|
40
|
+
EGP = "EGP",
|
|
41
|
+
ERN = "ERN",
|
|
42
|
+
ETB = "ETB",
|
|
43
|
+
EUR = "EUR",
|
|
44
|
+
FJD = "FJD",
|
|
45
|
+
FKP = "FKP",
|
|
46
|
+
GBP = "GBP",
|
|
47
|
+
GEL = "GEL",
|
|
48
|
+
GHS = "GHS",
|
|
49
|
+
GIP = "GIP",
|
|
50
|
+
GMD = "GMD",
|
|
51
|
+
GNF = "GNF",
|
|
52
|
+
GTQ = "GTQ",
|
|
53
|
+
GYD = "GYD",
|
|
54
|
+
HKD = "HKD",
|
|
55
|
+
HNL = "HNL",
|
|
56
|
+
HRK = "HRK",
|
|
57
|
+
HTG = "HTG",
|
|
58
|
+
HUF = "HUF",
|
|
59
|
+
IDR = "IDR",
|
|
60
|
+
ILS = "ILS",
|
|
61
|
+
INR = "INR",
|
|
62
|
+
IQD = "IQD",
|
|
63
|
+
IRR = "IRR",
|
|
64
|
+
ISK = "ISK",
|
|
65
|
+
JEP = "JEP",
|
|
66
|
+
JMD = "JMD",
|
|
67
|
+
JOD = "JOD",
|
|
68
|
+
JPY = "JPY",
|
|
69
|
+
KES = "KES",
|
|
70
|
+
KGS = "KGS",
|
|
71
|
+
KHR = "KHR",
|
|
72
|
+
KID = "KID",
|
|
73
|
+
KMF = "KMF",
|
|
74
|
+
KRW = "KRW",
|
|
75
|
+
KWD = "KWD",
|
|
76
|
+
KYD = "KYD",
|
|
77
|
+
KZT = "KZT",
|
|
78
|
+
LAK = "LAK",
|
|
79
|
+
LBP = "LBP",
|
|
80
|
+
LKR = "LKR",
|
|
81
|
+
LRD = "LRD",
|
|
82
|
+
LSL = "LSL",
|
|
83
|
+
LTL = "LTL",
|
|
84
|
+
LVL = "LVL",
|
|
85
|
+
LYD = "LYD",
|
|
86
|
+
MAD = "MAD",
|
|
87
|
+
MDL = "MDL",
|
|
88
|
+
MGA = "MGA",
|
|
89
|
+
MKD = "MKD",
|
|
90
|
+
MMK = "MMK",
|
|
91
|
+
MNT = "MNT",
|
|
92
|
+
MOP = "MOP",
|
|
93
|
+
MRU = "MRU",
|
|
94
|
+
MUR = "MUR",
|
|
95
|
+
MVR = "MVR",
|
|
96
|
+
MWK = "MWK",
|
|
97
|
+
MXN = "MXN",
|
|
98
|
+
MYR = "MYR",
|
|
99
|
+
MZN = "MZN",
|
|
100
|
+
NAD = "NAD",
|
|
101
|
+
NGN = "NGN",
|
|
102
|
+
NIO = "NIO",
|
|
103
|
+
NOK = "NOK",
|
|
104
|
+
NPR = "NPR",
|
|
105
|
+
NZD = "NZD",
|
|
106
|
+
OMR = "OMR",
|
|
107
|
+
PAB = "PAB",
|
|
108
|
+
PEN = "PEN",
|
|
109
|
+
PGK = "PGK",
|
|
110
|
+
PHP = "PHP",
|
|
111
|
+
PKR = "PKR",
|
|
112
|
+
PLN = "PLN",
|
|
113
|
+
PYG = "PYG",
|
|
114
|
+
QAR = "QAR",
|
|
115
|
+
RON = "RON",
|
|
116
|
+
RSD = "RSD",
|
|
117
|
+
RUB = "RUB",
|
|
118
|
+
RWF = "RWF",
|
|
119
|
+
SAR = "SAR",
|
|
120
|
+
SBD = "SBD",
|
|
121
|
+
SCR = "SCR",
|
|
122
|
+
SDG = "SDG",
|
|
123
|
+
SEK = "SEK",
|
|
124
|
+
SGD = "SGD",
|
|
125
|
+
SHP = "SHP",
|
|
126
|
+
SLL = "SLL",
|
|
127
|
+
SOS = "SOS",
|
|
128
|
+
SRD = "SRD",
|
|
129
|
+
SSP = "SSP",
|
|
130
|
+
STN = "STN",
|
|
131
|
+
SYP = "SYP",
|
|
132
|
+
SZL = "SZL",
|
|
133
|
+
THB = "THB",
|
|
134
|
+
TJS = "TJS",
|
|
135
|
+
TMT = "TMT",
|
|
136
|
+
TND = "TND",
|
|
137
|
+
TOP = "TOP",
|
|
138
|
+
TRY = "TRY",
|
|
139
|
+
TTD = "TTD",
|
|
140
|
+
TWD = "TWD",
|
|
141
|
+
TZS = "TZS",
|
|
142
|
+
UAH = "UAH",
|
|
143
|
+
UGX = "UGX",
|
|
144
|
+
USD = "USD",
|
|
145
|
+
UYU = "UYU",
|
|
146
|
+
UZS = "UZS",
|
|
147
|
+
VED = "VED",
|
|
148
|
+
VES = "VES",
|
|
149
|
+
VND = "VND",
|
|
150
|
+
VUV = "VUV",
|
|
151
|
+
WST = "WST",
|
|
152
|
+
XAF = "XAF",
|
|
153
|
+
XCD = "XCD",
|
|
154
|
+
XOF = "XOF",
|
|
155
|
+
XPF = "XPF",
|
|
156
|
+
XXX = "XXX",
|
|
157
|
+
YER = "YER",
|
|
158
|
+
ZAR = "ZAR",
|
|
159
|
+
ZMW = "ZMW",
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export enum CountryCode {
|
|
163
|
+
AC = "AC",
|
|
164
|
+
AD = "AD",
|
|
165
|
+
AE = "AE",
|
|
166
|
+
AF = "AF",
|
|
167
|
+
AG = "AG",
|
|
168
|
+
AI = "AI",
|
|
169
|
+
AL = "AL",
|
|
170
|
+
AM = "AM",
|
|
171
|
+
AN = "AN",
|
|
172
|
+
AO = "AO",
|
|
173
|
+
AR = "AR",
|
|
174
|
+
AT = "AT",
|
|
175
|
+
AU = "AU",
|
|
176
|
+
AW = "AW",
|
|
177
|
+
AX = "AX",
|
|
178
|
+
AZ = "AZ",
|
|
179
|
+
BA = "BA",
|
|
180
|
+
BB = "BB",
|
|
181
|
+
BD = "BD",
|
|
182
|
+
BE = "BE",
|
|
183
|
+
BF = "BF",
|
|
184
|
+
BG = "BG",
|
|
185
|
+
BH = "BH",
|
|
186
|
+
BI = "BI",
|
|
187
|
+
BJ = "BJ",
|
|
188
|
+
BL = "BL",
|
|
189
|
+
BM = "BM",
|
|
190
|
+
BN = "BN",
|
|
191
|
+
BO = "BO",
|
|
192
|
+
BQ = "BQ",
|
|
193
|
+
BR = "BR",
|
|
194
|
+
BS = "BS",
|
|
195
|
+
BT = "BT",
|
|
196
|
+
BV = "BV",
|
|
197
|
+
BW = "BW",
|
|
198
|
+
BY = "BY",
|
|
199
|
+
BZ = "BZ",
|
|
200
|
+
CA = "CA",
|
|
201
|
+
CC = "CC",
|
|
202
|
+
CD = "CD",
|
|
203
|
+
CF = "CF",
|
|
204
|
+
CG = "CG",
|
|
205
|
+
CH = "CH",
|
|
206
|
+
CI = "CI",
|
|
207
|
+
CK = "CK",
|
|
208
|
+
CL = "CL",
|
|
209
|
+
CM = "CM",
|
|
210
|
+
CN = "CN",
|
|
211
|
+
CO = "CO",
|
|
212
|
+
CR = "CR",
|
|
213
|
+
CU = "CU",
|
|
214
|
+
CV = "CV",
|
|
215
|
+
CW = "CW",
|
|
216
|
+
CX = "CX",
|
|
217
|
+
CY = "CY",
|
|
218
|
+
CZ = "CZ",
|
|
219
|
+
DE = "DE",
|
|
220
|
+
DJ = "DJ",
|
|
221
|
+
DK = "DK",
|
|
222
|
+
DM = "DM",
|
|
223
|
+
DO = "DO",
|
|
224
|
+
DZ = "DZ",
|
|
225
|
+
EC = "EC",
|
|
226
|
+
EE = "EE",
|
|
227
|
+
EG = "EG",
|
|
228
|
+
EH = "EH",
|
|
229
|
+
ER = "ER",
|
|
230
|
+
ES = "ES",
|
|
231
|
+
ET = "ET",
|
|
232
|
+
FI = "FI",
|
|
233
|
+
FJ = "FJ",
|
|
234
|
+
FK = "FK",
|
|
235
|
+
FO = "FO",
|
|
236
|
+
FR = "FR",
|
|
237
|
+
GA = "GA",
|
|
238
|
+
GB = "GB",
|
|
239
|
+
GD = "GD",
|
|
240
|
+
GE = "GE",
|
|
241
|
+
GF = "GF",
|
|
242
|
+
GG = "GG",
|
|
243
|
+
GH = "GH",
|
|
244
|
+
GI = "GI",
|
|
245
|
+
GL = "GL",
|
|
246
|
+
GM = "GM",
|
|
247
|
+
GN = "GN",
|
|
248
|
+
GP = "GP",
|
|
249
|
+
GQ = "GQ",
|
|
250
|
+
GR = "GR",
|
|
251
|
+
GS = "GS",
|
|
252
|
+
GT = "GT",
|
|
253
|
+
GW = "GW",
|
|
254
|
+
GY = "GY",
|
|
255
|
+
HK = "HK",
|
|
256
|
+
HM = "HM",
|
|
257
|
+
HN = "HN",
|
|
258
|
+
HR = "HR",
|
|
259
|
+
HT = "HT",
|
|
260
|
+
HU = "HU",
|
|
261
|
+
ID = "ID",
|
|
262
|
+
IE = "IE",
|
|
263
|
+
IL = "IL",
|
|
264
|
+
IM = "IM",
|
|
265
|
+
IN = "IN",
|
|
266
|
+
IO = "IO",
|
|
267
|
+
IQ = "IQ",
|
|
268
|
+
IR = "IR",
|
|
269
|
+
IS = "IS",
|
|
270
|
+
IT = "IT",
|
|
271
|
+
JE = "JE",
|
|
272
|
+
JM = "JM",
|
|
273
|
+
JO = "JO",
|
|
274
|
+
JP = "JP",
|
|
275
|
+
KE = "KE",
|
|
276
|
+
KG = "KG",
|
|
277
|
+
KH = "KH",
|
|
278
|
+
KI = "KI",
|
|
279
|
+
KM = "KM",
|
|
280
|
+
KN = "KN",
|
|
281
|
+
KP = "KP",
|
|
282
|
+
KR = "KR",
|
|
283
|
+
KW = "KW",
|
|
284
|
+
KY = "KY",
|
|
285
|
+
KZ = "KZ",
|
|
286
|
+
LA = "LA",
|
|
287
|
+
LB = "LB",
|
|
288
|
+
LC = "LC",
|
|
289
|
+
LI = "LI",
|
|
290
|
+
LK = "LK",
|
|
291
|
+
LR = "LR",
|
|
292
|
+
LS = "LS",
|
|
293
|
+
LT = "LT",
|
|
294
|
+
LU = "LU",
|
|
295
|
+
LV = "LV",
|
|
296
|
+
LY = "LY",
|
|
297
|
+
MA = "MA",
|
|
298
|
+
MC = "MC",
|
|
299
|
+
MD = "MD",
|
|
300
|
+
ME = "ME",
|
|
301
|
+
MF = "MF",
|
|
302
|
+
MG = "MG",
|
|
303
|
+
MK = "MK",
|
|
304
|
+
ML = "ML",
|
|
305
|
+
MM = "MM",
|
|
306
|
+
MN = "MN",
|
|
307
|
+
MO = "MO",
|
|
308
|
+
MQ = "MQ",
|
|
309
|
+
MR = "MR",
|
|
310
|
+
MS = "MS",
|
|
311
|
+
MT = "MT",
|
|
312
|
+
MU = "MU",
|
|
313
|
+
MV = "MV",
|
|
314
|
+
MW = "MW",
|
|
315
|
+
MX = "MX",
|
|
316
|
+
MY = "MY",
|
|
317
|
+
MZ = "MZ",
|
|
318
|
+
NA = "NA",
|
|
319
|
+
NC = "NC",
|
|
320
|
+
NE = "NE",
|
|
321
|
+
NF = "NF",
|
|
322
|
+
NG = "NG",
|
|
323
|
+
NI = "NI",
|
|
324
|
+
NL = "NL",
|
|
325
|
+
NO = "NO",
|
|
326
|
+
NP = "NP",
|
|
327
|
+
NR = "NR",
|
|
328
|
+
NU = "NU",
|
|
329
|
+
NZ = "NZ",
|
|
330
|
+
OM = "OM",
|
|
331
|
+
PA = "PA",
|
|
332
|
+
PE = "PE",
|
|
333
|
+
PF = "PF",
|
|
334
|
+
PG = "PG",
|
|
335
|
+
PH = "PH",
|
|
336
|
+
PK = "PK",
|
|
337
|
+
PL = "PL",
|
|
338
|
+
PM = "PM",
|
|
339
|
+
PN = "PN",
|
|
340
|
+
PS = "PS",
|
|
341
|
+
PT = "PT",
|
|
342
|
+
PY = "PY",
|
|
343
|
+
QA = "QA",
|
|
344
|
+
RE = "RE",
|
|
345
|
+
RO = "RO",
|
|
346
|
+
RS = "RS",
|
|
347
|
+
RU = "RU",
|
|
348
|
+
RW = "RW",
|
|
349
|
+
SA = "SA",
|
|
350
|
+
SB = "SB",
|
|
351
|
+
SC = "SC",
|
|
352
|
+
SD = "SD",
|
|
353
|
+
SE = "SE",
|
|
354
|
+
SG = "SG",
|
|
355
|
+
SH = "SH",
|
|
356
|
+
SI = "SI",
|
|
357
|
+
SJ = "SJ",
|
|
358
|
+
SK = "SK",
|
|
359
|
+
SL = "SL",
|
|
360
|
+
SM = "SM",
|
|
361
|
+
SN = "SN",
|
|
362
|
+
SO = "SO",
|
|
363
|
+
SR = "SR",
|
|
364
|
+
SS = "SS",
|
|
365
|
+
ST = "ST",
|
|
366
|
+
SV = "SV",
|
|
367
|
+
SX = "SX",
|
|
368
|
+
SY = "SY",
|
|
369
|
+
SZ = "SZ",
|
|
370
|
+
TA = "TA",
|
|
371
|
+
TC = "TC",
|
|
372
|
+
TD = "TD",
|
|
373
|
+
TF = "TF",
|
|
374
|
+
TG = "TG",
|
|
375
|
+
TH = "TH",
|
|
376
|
+
TJ = "TJ",
|
|
377
|
+
TK = "TK",
|
|
378
|
+
TL = "TL",
|
|
379
|
+
TM = "TM",
|
|
380
|
+
TN = "TN",
|
|
381
|
+
TO = "TO",
|
|
382
|
+
TR = "TR",
|
|
383
|
+
TT = "TT",
|
|
384
|
+
TV = "TV",
|
|
385
|
+
TW = "TW",
|
|
386
|
+
TZ = "TZ",
|
|
387
|
+
UA = "UA",
|
|
388
|
+
UG = "UG",
|
|
389
|
+
UM = "UM",
|
|
390
|
+
US = "US",
|
|
391
|
+
UY = "UY",
|
|
392
|
+
UZ = "UZ",
|
|
393
|
+
VA = "VA",
|
|
394
|
+
VC = "VC",
|
|
395
|
+
VE = "VE",
|
|
396
|
+
VG = "VG",
|
|
397
|
+
VN = "VN",
|
|
398
|
+
VU = "VU",
|
|
399
|
+
WF = "WF",
|
|
400
|
+
WS = "WS",
|
|
401
|
+
XK = "XK",
|
|
402
|
+
YE = "YE",
|
|
403
|
+
YT = "YT",
|
|
404
|
+
ZA = "ZA",
|
|
405
|
+
ZM = "ZM",
|
|
406
|
+
ZW = "ZW",
|
|
407
|
+
ZZ = "ZZ",
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export enum OrderCancelReason {
|
|
411
|
+
CUSTOMER = "CUSTOMER",
|
|
412
|
+
DECLINED = "DECLINED",
|
|
413
|
+
FRAUD = "FRAUD",
|
|
414
|
+
INVENTORY = "INVENTORY",
|
|
415
|
+
OTHER = "OTHER",
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export enum OrderFinancialStatus {
|
|
419
|
+
AUTHORIZED = "AUTHORIZED",
|
|
420
|
+
PAID = "PAID",
|
|
421
|
+
PARTIALLY_PAID = "PARTIALLY_PAID",
|
|
422
|
+
PARTIALLY_REFUNDED = "PARTIALLY_REFUNDED",
|
|
423
|
+
PENDING = "PENDING",
|
|
424
|
+
REFUNDED = "REFUNDED",
|
|
425
|
+
VOIDED = "VOIDED",
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export enum OrderFulfillmentStatus {
|
|
429
|
+
FULFILLED = "FULFILLED",
|
|
430
|
+
IN_PROGRESS = "IN_PROGRESS",
|
|
431
|
+
ON_HOLD = "ON_HOLD",
|
|
432
|
+
OPEN = "OPEN",
|
|
433
|
+
PARTIALLY_FULFILLED = "PARTIALLY_FULFILLED",
|
|
434
|
+
PENDING_FULFILLMENT = "PENDING_FULFILLMENT",
|
|
435
|
+
RESTOCKED = "RESTOCKED",
|
|
436
|
+
SCHEDULED = "SCHEDULED",
|
|
437
|
+
UNFULFILLED = "UNFULFILLED",
|
|
438
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { InstrumentedFetchInit } from "@decocms/blocks/sdk/instrumentedFetch";
|
|
2
|
+
import { extractGraphqlOperationName } from "./graphqlOperationName";
|
|
3
|
+
|
|
4
|
+
export function gql(strings: TemplateStringsArray, ...values: unknown[]): string {
|
|
5
|
+
return strings.reduce((acc, str, i) => acc + str + (values[i] ?? ""), "");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface QueryDefinition {
|
|
9
|
+
fragments?: string[];
|
|
10
|
+
query: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function buildQuery(def: QueryDefinition): string {
|
|
14
|
+
const fragments = def.fragments?.join("\n") ?? "";
|
|
15
|
+
return fragments ? `${fragments}\n${def.query}` : def.query;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface GraphQLClient {
|
|
19
|
+
query<T>(query: string | QueryDefinition, variables?: Record<string, unknown>): Promise<T>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createGraphqlClient(
|
|
23
|
+
endpoint: string,
|
|
24
|
+
headers: Record<string, string>,
|
|
25
|
+
fetchFn?: typeof fetch,
|
|
26
|
+
): GraphQLClient {
|
|
27
|
+
const _fetch = fetchFn ?? globalThis.fetch;
|
|
28
|
+
return {
|
|
29
|
+
async query<T>(
|
|
30
|
+
queryOrDef: string | QueryDefinition,
|
|
31
|
+
variables?: Record<string, unknown>,
|
|
32
|
+
): Promise<T> {
|
|
33
|
+
const query = typeof queryOrDef === "string" ? queryOrDef : buildQuery(queryOrDef);
|
|
34
|
+
|
|
35
|
+
// Stamp the GraphQL operation as init.operation so the framework's
|
|
36
|
+
// span name becomes `shopify.<OperationName>` instead of the
|
|
37
|
+
// generic `shopify.storefront.graphql` from the URL router. The
|
|
38
|
+
// extra field is silently dropped by plain `fetch` and read by
|
|
39
|
+
// any `InstrumentedFetch` configured via `setShopifyFetch`.
|
|
40
|
+
const operation = extractGraphqlOperationName(query);
|
|
41
|
+
const init: InstrumentedFetchInit = {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: {
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
...headers,
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify({ query, variables }),
|
|
48
|
+
...(operation ? { operation } : {}),
|
|
49
|
+
};
|
|
50
|
+
const response = await _fetch(endpoint, init);
|
|
51
|
+
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
throw new Error(`Shopify GraphQL error: ${response.status} ${response.statusText}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const json = (await response.json()) as { data?: T; errors?: Array<{ message: string }> };
|
|
57
|
+
|
|
58
|
+
if (json.errors?.length) {
|
|
59
|
+
throw new Error(`Shopify GraphQL errors: ${json.errors.map((e) => e.message).join(", ")}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (json.data === undefined) {
|
|
63
|
+
throw new Error("Shopify GraphQL response missing data");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return json.data;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract a semantic operation name from a GraphQL document.
|
|
3
|
+
*
|
|
4
|
+
* Used at the Shopify GraphQL client layer to stamp `init.operation`
|
|
5
|
+
* on the outbound fetch. The framework then suffixes the integration
|
|
6
|
+
* name (`shopify.<operation>`) onto the span and uses the same string
|
|
7
|
+
* as the `fetch.operation` attribute + histogram label.
|
|
8
|
+
*
|
|
9
|
+
* Resolution order:
|
|
10
|
+
*
|
|
11
|
+
* 1. An explicit `operationName` argument (e.g. when the client
|
|
12
|
+
* received one alongside a multi-operation document) wins.
|
|
13
|
+
* 2. If the document has exactly one named operation, that name
|
|
14
|
+
* is used.
|
|
15
|
+
* 3. If the document has zero or many anonymous operations, we
|
|
16
|
+
* return `undefined` so the caller can fall back (typically to
|
|
17
|
+
* the URL-derived `storefront.graphql` / `admin.graphql`).
|
|
18
|
+
*
|
|
19
|
+
* The parser is deliberately a small regex pass, not a full GraphQL
|
|
20
|
+
* tokenizer:
|
|
21
|
+
*
|
|
22
|
+
* - GraphQL operation definitions live at the top level of the
|
|
23
|
+
* document, never nested inside other operations, fragments, or
|
|
24
|
+
* selection sets, so positional context isn't required to find
|
|
25
|
+
* them — only to not match the literal words `query` /
|
|
26
|
+
* `mutation` / `subscription` inside string values.
|
|
27
|
+
* - We strip block strings (`""" … """`), string literals
|
|
28
|
+
* (`"…"`), and `# …` comments before matching, which is enough
|
|
29
|
+
* to make false-positive matches inside comments / docs vanish.
|
|
30
|
+
*
|
|
31
|
+
* If a Shopify operation is ever sufficiently mis-named to break
|
|
32
|
+
* this (unlikely, since the storefront SDK names them deliberately),
|
|
33
|
+
* the caller can always set `init.operation` explicitly.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const OPERATION_RE = /\b(?:query|mutation|subscription)\s+([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
37
|
+
|
|
38
|
+
const stripCommentsAndStrings = (doc: string): string =>
|
|
39
|
+
doc
|
|
40
|
+
.replace(/"""[\s\S]*?"""/g, '""')
|
|
41
|
+
.replace(/"(?:\\.|[^"\\])*"/g, '""')
|
|
42
|
+
.replace(/#[^\n]*/g, "");
|
|
43
|
+
|
|
44
|
+
export function extractGraphqlOperationName(
|
|
45
|
+
document: string,
|
|
46
|
+
explicit?: string,
|
|
47
|
+
): string | undefined {
|
|
48
|
+
if (explicit) return explicit;
|
|
49
|
+
if (!document) return undefined;
|
|
50
|
+
|
|
51
|
+
const stripped = stripCommentsAndStrings(document);
|
|
52
|
+
const names: string[] = [];
|
|
53
|
+
|
|
54
|
+
OPERATION_RE.lastIndex = 0;
|
|
55
|
+
for (
|
|
56
|
+
let match = OPERATION_RE.exec(stripped);
|
|
57
|
+
match !== null;
|
|
58
|
+
match = OPERATION_RE.exec(stripped)
|
|
59
|
+
) {
|
|
60
|
+
const [, name] = match;
|
|
61
|
+
if (name) names.push(name);
|
|
62
|
+
if (names.length > 1) break;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (names.length === 1) return names[0];
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-wired instrumented fetch factory for Shopify.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `vtex/utils/instrumentedFetch.ts`. Bundles:
|
|
5
|
+
*
|
|
6
|
+
* 1. `createInstrumentedFetch` from `@decocms/start` (spans,
|
|
7
|
+
* traceparent, URL redaction).
|
|
8
|
+
* 2. `shopifyOperationRouter` as the URL fallback for non-GraphQL
|
|
9
|
+
* and unnamed-GraphQL calls.
|
|
10
|
+
* 3. An `onComplete` that records the canonical
|
|
11
|
+
* `http.client.request.duration` histogram (via the framework's
|
|
12
|
+
* `recordCommerceMetric(...)` helper) with `provider: "shopify"`.
|
|
13
|
+
*
|
|
14
|
+
* Sites do:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { setShopifyFetch, createShopifyFetch } from "@decocms/apps/shopify";
|
|
18
|
+
* setShopifyFetch(createShopifyFetch());
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Per-call operation names come from `extractGraphqlOperationName`
|
|
22
|
+
* (wired in `./graphql.ts`); the URL router fires only when the
|
|
23
|
+
* extractor returns `undefined`.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import {
|
|
27
|
+
createInstrumentedFetch,
|
|
28
|
+
type InstrumentedFetch,
|
|
29
|
+
} from "@decocms/blocks/sdk/instrumentedFetch";
|
|
30
|
+
import { recordCommerceMetric } from "@decocms/blocks/sdk/observability";
|
|
31
|
+
import { shopifyOperationRouter } from "./operationRouter";
|
|
32
|
+
|
|
33
|
+
export interface CreateShopifyFetchOptions {
|
|
34
|
+
baseFetch?: typeof fetch;
|
|
35
|
+
disableHistogram?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function createShopifyFetch(options: CreateShopifyFetchOptions = {}): InstrumentedFetch {
|
|
39
|
+
const { baseFetch, disableHistogram = false } = options;
|
|
40
|
+
return createInstrumentedFetch({
|
|
41
|
+
name: "shopify",
|
|
42
|
+
baseFetch,
|
|
43
|
+
resolveOperation: shopifyOperationRouter,
|
|
44
|
+
onComplete: disableHistogram
|
|
45
|
+
? undefined
|
|
46
|
+
: ({ operation, status, durationMs, cached }) => {
|
|
47
|
+
recordCommerceMetric(durationMs, {
|
|
48
|
+
provider: "shopify",
|
|
49
|
+
operation,
|
|
50
|
+
status_class: `${Math.floor(status / 100)}xx`,
|
|
51
|
+
cached,
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL-derived operation name router for Shopify API calls.
|
|
3
|
+
*
|
|
4
|
+
* Plugged into `@decocms/start`'s `createInstrumentedFetch` via the
|
|
5
|
+
* `resolveOperation(url, method)` option. Mirrors the shape of the
|
|
6
|
+
* VTEX router in `../../vtex/utils/operationRouter.ts`.
|
|
7
|
+
*
|
|
8
|
+
* Shopify's API surface from this repo is overwhelmingly GraphQL —
|
|
9
|
+
* a single endpoint per environment (storefront vs admin). That means
|
|
10
|
+
* the URL alone can only tell us *which GraphQL surface* a call is
|
|
11
|
+
* hitting, not what the call actually does. The semantic operation
|
|
12
|
+
* name lives in the GraphQL document itself (`query Foo { ... }`),
|
|
13
|
+
* and is extracted by `extractGraphqlOperationName` (see
|
|
14
|
+
* `./graphqlOperationName.ts`) at the client layer and stamped as
|
|
15
|
+
* `init.operation`, which always wins over this router.
|
|
16
|
+
*
|
|
17
|
+
* So this router exists for:
|
|
18
|
+
*
|
|
19
|
+
* - non-GraphQL Shopify REST endpoints we may add later
|
|
20
|
+
* (cart API, customer accounts, billing, etc.);
|
|
21
|
+
* - giving the GraphQL endpoints a *fallback* operation when the
|
|
22
|
+
* extractor can't parse a name (anonymous queries, missing body).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
type OperationResolver = string | ((match: RegExpMatchArray, method: string) => string);
|
|
26
|
+
|
|
27
|
+
interface Matcher {
|
|
28
|
+
pattern: RegExp;
|
|
29
|
+
operation: OperationResolver;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const m = (pattern: RegExp, operation: OperationResolver): Matcher => ({ pattern, operation });
|
|
33
|
+
|
|
34
|
+
const MATCHERS: ReadonlyArray<Matcher> = [
|
|
35
|
+
m(/^\/admin\/api\/[0-9]{4}-[0-9]{2}\/graphql\.json/, "admin.graphql"),
|
|
36
|
+
m(/^\/api\/[0-9]{4}-[0-9]{2}\/graphql\.json/, "storefront.graphql"),
|
|
37
|
+
|
|
38
|
+
m(/^\/admin\/api\/[0-9]{4}-[0-9]{2}\/products/, "admin.products"),
|
|
39
|
+
m(/^\/admin\/api\/[0-9]{4}-[0-9]{2}\/orders/, "admin.orders"),
|
|
40
|
+
m(/^\/admin\/api\/[0-9]{4}-[0-9]{2}\/customers/, "admin.customers"),
|
|
41
|
+
m(/^\/admin\/api\/[0-9]{4}-[0-9]{2}\/inventory/, "admin.inventory"),
|
|
42
|
+
|
|
43
|
+
m(/^\/api\/[0-9]{4}-[0-9]{2}\/checkouts/, "storefront.checkout"),
|
|
44
|
+
m(/^\/cart(?:\/|\.js|$)/, "storefront.cart"),
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolve an operation name for a Shopify URL. Returns `undefined`
|
|
49
|
+
* if no matcher fires, which causes the framework to fall back to
|
|
50
|
+
* `shopify.fetch`.
|
|
51
|
+
*/
|
|
52
|
+
export function shopifyOperationRouter(url: string, method: string): string | undefined {
|
|
53
|
+
let pathname: string;
|
|
54
|
+
try {
|
|
55
|
+
pathname = new URL(url).pathname;
|
|
56
|
+
} catch {
|
|
57
|
+
const qs = url.indexOf("?");
|
|
58
|
+
const hash = url.indexOf("#");
|
|
59
|
+
const end = [qs, hash].filter((i) => i >= 0).sort((a, b) => a - b)[0];
|
|
60
|
+
pathname = end === undefined ? url : url.slice(0, end);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const upperMethod = method.toUpperCase();
|
|
64
|
+
for (const { pattern, operation } of MATCHERS) {
|
|
65
|
+
const match = pathname.match(pattern);
|
|
66
|
+
if (!match) continue;
|
|
67
|
+
return typeof operation === "function" ? operation(match, upperMethod) : operation;
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|