@shopify/hydrogen 2026.1.3 → 2026.4.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/dist/customer-account-api-types.d.ts +23 -1
- package/dist/customer-account.schema.json +1 -1
- package/dist/development/index.cjs +135 -58
- package/dist/development/index.cjs.map +1 -1
- package/dist/development/index.js +135 -58
- package/dist/development/index.js.map +1 -1
- package/dist/oxygen/index.d.ts +62 -9
- package/dist/oxygen/index.js +21 -30
- package/dist/production/index.cjs +52 -52
- package/dist/production/index.cjs.map +1 -1
- package/dist/production/index.d.cts +126 -115
- package/dist/production/index.d.ts +126 -115
- package/dist/production/index.js +52 -52
- package/dist/production/index.js.map +1 -1
- package/dist/storefront-api-types.d.ts +1467 -443
- package/dist/storefront.schema.json +1 -1
- package/dist/vite/request-events.d.ts +61 -1
- package/package.json +6 -6
|
@@ -443,7 +443,8 @@ function useCustomerPrivacy(props) {
|
|
|
443
443
|
react.useEffect(() => {
|
|
444
444
|
if (observing.current.customerPrivacy) return;
|
|
445
445
|
observing.current.customerPrivacy = true;
|
|
446
|
-
let
|
|
446
|
+
let backendConsentStub = null;
|
|
447
|
+
let fullCustomerPrivacy = null;
|
|
447
448
|
let customShopify = window.Shopify || void 0;
|
|
448
449
|
Object.defineProperty(window, "Shopify", {
|
|
449
450
|
configurable: true,
|
|
@@ -453,15 +454,16 @@ function useCustomerPrivacy(props) {
|
|
|
453
454
|
set(value) {
|
|
454
455
|
if (typeof value === "object" && value !== null && Object.keys(value).length === 0) {
|
|
455
456
|
customShopify = value;
|
|
457
|
+
backendConsentStub = { backendConsentEnabled: true };
|
|
456
458
|
Object.defineProperty(window.Shopify, "customerPrivacy", {
|
|
457
459
|
configurable: true,
|
|
458
460
|
get() {
|
|
459
|
-
return
|
|
461
|
+
return fullCustomerPrivacy ?? backendConsentStub;
|
|
460
462
|
},
|
|
461
463
|
set(value2) {
|
|
462
464
|
if (typeof value2 === "object" && value2 !== null && "setTrackingConsent" in value2) {
|
|
463
465
|
const customerPrivacy = value2;
|
|
464
|
-
|
|
466
|
+
fullCustomerPrivacy = {
|
|
465
467
|
...customerPrivacy,
|
|
466
468
|
// Note: this method is not used by the privacy-banner,
|
|
467
469
|
// it bundles its own setTrackingConsent.
|
|
@@ -471,7 +473,7 @@ function useCustomerPrivacy(props) {
|
|
|
471
473
|
};
|
|
472
474
|
customShopify = {
|
|
473
475
|
...customShopify,
|
|
474
|
-
customerPrivacy:
|
|
476
|
+
customerPrivacy: fullCustomerPrivacy
|
|
475
477
|
};
|
|
476
478
|
setLoaded.customerPrivacy();
|
|
477
479
|
}
|
|
@@ -596,7 +598,8 @@ function overridePrivacyBannerMethods({
|
|
|
596
598
|
}
|
|
597
599
|
function getCustomerPrivacy() {
|
|
598
600
|
try {
|
|
599
|
-
|
|
601
|
+
const cp = window.Shopify?.customerPrivacy;
|
|
602
|
+
return cp && "setTrackingConsent" in cp ? cp : null;
|
|
600
603
|
} catch (e) {
|
|
601
604
|
return null;
|
|
602
605
|
}
|
|
@@ -610,7 +613,7 @@ function getPrivacyBanner() {
|
|
|
610
613
|
}
|
|
611
614
|
|
|
612
615
|
// package.json
|
|
613
|
-
var version = "2026.
|
|
616
|
+
var version = "2026.4.0";
|
|
614
617
|
|
|
615
618
|
// src/analytics-manager/ShopifyAnalytics.tsx
|
|
616
619
|
function getCustomerPrivacyRequired() {
|
|
@@ -1133,7 +1136,7 @@ function register(key) {
|
|
|
1133
1136
|
}
|
|
1134
1137
|
function shopifyCanTrack() {
|
|
1135
1138
|
try {
|
|
1136
|
-
return window.Shopify.customerPrivacy
|
|
1139
|
+
return window.Shopify.customerPrivacy?.analyticsProcessingAllowed?.() ?? false;
|
|
1137
1140
|
} catch (e) {
|
|
1138
1141
|
}
|
|
1139
1142
|
return false;
|
|
@@ -1326,6 +1329,7 @@ function getStorefrontHeaders(request) {
|
|
|
1326
1329
|
};
|
|
1327
1330
|
}
|
|
1328
1331
|
var SFAPI_RE = /^\/api\/(unstable|2\d{3}-\d{2})\/graphql\.json$/;
|
|
1332
|
+
var MCP_RE = /^\/api\/mcp$/;
|
|
1329
1333
|
var getSafePathname = (url) => {
|
|
1330
1334
|
try {
|
|
1331
1335
|
return new URL(url, "http://e.c").pathname;
|
|
@@ -2005,7 +2009,7 @@ function generateUUID() {
|
|
|
2005
2009
|
}
|
|
2006
2010
|
|
|
2007
2011
|
// src/version.ts
|
|
2008
|
-
var LIB_VERSION = "2026.
|
|
2012
|
+
var LIB_VERSION = "2026.4.0";
|
|
2009
2013
|
|
|
2010
2014
|
// src/utils/graphql.ts
|
|
2011
2015
|
function minifyQuery(string) {
|
|
@@ -2430,6 +2434,70 @@ function createStorefrontClient(options) {
|
|
|
2430
2434
|
);
|
|
2431
2435
|
return new Response(sfapiResponse.body, sfapiResponse);
|
|
2432
2436
|
},
|
|
2437
|
+
/**
|
|
2438
|
+
* Checks if the request is targeting the Storefront MCP endpoint.
|
|
2439
|
+
*/
|
|
2440
|
+
isMcpUrl(request) {
|
|
2441
|
+
return MCP_RE.test(getSafePathname(request.url ?? ""));
|
|
2442
|
+
},
|
|
2443
|
+
/**
|
|
2444
|
+
* Forwards the request to the Storefront MCP endpoint.
|
|
2445
|
+
* CORS headers are intentionally omitted — the Storefront MCP
|
|
2446
|
+
* server is server-to-server only (OPTIONS preflight returns 404).
|
|
2447
|
+
*/
|
|
2448
|
+
async forwardMcp(request) {
|
|
2449
|
+
const forwardedHeaders = new Headers([
|
|
2450
|
+
...extractHeaders(
|
|
2451
|
+
(key) => request.headers.get(key),
|
|
2452
|
+
[
|
|
2453
|
+
"accept",
|
|
2454
|
+
"accept-encoding",
|
|
2455
|
+
"accept-language",
|
|
2456
|
+
"content-type",
|
|
2457
|
+
"cookie",
|
|
2458
|
+
"origin",
|
|
2459
|
+
"referer",
|
|
2460
|
+
"user-agent"
|
|
2461
|
+
]
|
|
2462
|
+
),
|
|
2463
|
+
...extractHeaders(
|
|
2464
|
+
(key) => defaultHeaders[key],
|
|
2465
|
+
[
|
|
2466
|
+
SHOPIFY_CLIENT_IP_HEADER,
|
|
2467
|
+
SHOPIFY_CLIENT_IP_SIG_HEADER,
|
|
2468
|
+
STOREFRONT_ACCESS_TOKEN_HEADER,
|
|
2469
|
+
STOREFRONT_REQUEST_GROUP_ID_HEADER,
|
|
2470
|
+
hydrogenReact.SHOPIFY_STOREFRONT_ID_HEADER
|
|
2471
|
+
]
|
|
2472
|
+
)
|
|
2473
|
+
]);
|
|
2474
|
+
if (storefrontHeaders?.buyerIp) {
|
|
2475
|
+
forwardedHeaders.set("x-forwarded-for", storefrontHeaders.buyerIp);
|
|
2476
|
+
}
|
|
2477
|
+
const mcpUrl = `${getShopifyDomain()}/api/mcp`;
|
|
2478
|
+
try {
|
|
2479
|
+
const mcpResponse = await fetch(mcpUrl, {
|
|
2480
|
+
method: request.method,
|
|
2481
|
+
body: request.body,
|
|
2482
|
+
headers: forwardedHeaders
|
|
2483
|
+
});
|
|
2484
|
+
return new Response(mcpResponse.body, mcpResponse);
|
|
2485
|
+
} catch (error) {
|
|
2486
|
+
const JSON_RPC_INTERNAL_ERROR = -32603;
|
|
2487
|
+
const message = error instanceof Error ? error.message : "Internal proxy error";
|
|
2488
|
+
return new Response(
|
|
2489
|
+
JSON.stringify({
|
|
2490
|
+
jsonrpc: "2.0",
|
|
2491
|
+
error: { code: JSON_RPC_INTERNAL_ERROR, message },
|
|
2492
|
+
id: null
|
|
2493
|
+
}),
|
|
2494
|
+
{
|
|
2495
|
+
status: 502,
|
|
2496
|
+
headers: { "content-type": "application/json" }
|
|
2497
|
+
}
|
|
2498
|
+
);
|
|
2499
|
+
}
|
|
2500
|
+
},
|
|
2433
2501
|
setCollectedSubrequestHeaders: (response) => {
|
|
2434
2502
|
if (collectedSubrequestHeaders) {
|
|
2435
2503
|
for (const value of collectedSubrequestHeaders.setCookie) {
|
|
@@ -3596,7 +3664,7 @@ var hydrogenContext = {
|
|
|
3596
3664
|
};
|
|
3597
3665
|
|
|
3598
3666
|
// src/customer/constants.ts
|
|
3599
|
-
var DEFAULT_CUSTOMER_API_VERSION = "2026-
|
|
3667
|
+
var DEFAULT_CUSTOMER_API_VERSION = "2026-04";
|
|
3600
3668
|
var USER_AGENT = `Shopify Hydrogen ${LIB_VERSION}`;
|
|
3601
3669
|
var CUSTOMER_API_CLIENT_ID = "30243aa5-17c1-465a-8493-944bcc4e88aa";
|
|
3602
3670
|
var CUSTOMER_ACCOUNT_SESSION_KEY = "customerAccount";
|
|
@@ -3923,29 +3991,32 @@ function createCustomerAccountHelper(customerApiVersion, shopId) {
|
|
|
3923
3991
|
|
|
3924
3992
|
// src/customer/customer.ts
|
|
3925
3993
|
var HYDROGEN_TUNNEL_DOMAIN_SUFFIX = ".tryhydrogen.dev";
|
|
3926
|
-
function
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
].join("\n\n"),
|
|
3935
|
-
{
|
|
3936
|
-
status: 400,
|
|
3937
|
-
headers: {
|
|
3938
|
-
"Content-Type": "text/plain; charset=utf-8"
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
);
|
|
3942
|
-
}
|
|
3994
|
+
function checkTunnelDomain(hostname, useCustomAuthDomain, redirectUri) {
|
|
3995
|
+
if (hostname.endsWith(HYDROGEN_TUNNEL_DOMAIN_SUFFIX)) return;
|
|
3996
|
+
if (useCustomAuthDomain) {
|
|
3997
|
+
const redirectHint = redirectUri ? ` (${redirectUri})` : "";
|
|
3998
|
+
warnOnce(
|
|
3999
|
+
`[h2:warn:customerAccount] You are using a custom domain (${hostname}) instead of a Hydrogen dev tunnel. Make sure you have manually registered your redirect_uri${redirectHint} in your Customer Account API settings in the Shopify admin. See https://shopify.dev/docs/api/customer for details.`
|
|
4000
|
+
);
|
|
4001
|
+
return;
|
|
3943
4002
|
}
|
|
4003
|
+
throw new Response(
|
|
4004
|
+
[
|
|
4005
|
+
"Customer Account API OAuth requires a Hydrogen tunnel in local development.",
|
|
4006
|
+
"Run the development server with the `--customer-account-push` flag,",
|
|
4007
|
+
`then open the tunnel URL shown in your terminal (\`https://*${HYDROGEN_TUNNEL_DOMAIN_SUFFIX}\`) instead of localhost.`
|
|
4008
|
+
].join("\n\n"),
|
|
4009
|
+
{
|
|
4010
|
+
status: 400,
|
|
4011
|
+
headers: {
|
|
4012
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
);
|
|
3944
4016
|
}
|
|
3945
4017
|
function defaultAuthStatusHandler(request, defaultLoginUrl) {
|
|
3946
4018
|
if (!request.url) return defaultLoginUrl;
|
|
3947
|
-
const {
|
|
3948
|
-
throwIfNotTunnelled(hostname);
|
|
4019
|
+
const { pathname } = new URL(request.url);
|
|
3949
4020
|
const cleanedPathname = pathname.replace(/\.data$/, "").replace(/\/_root$/, "/").replace(/(.+)\/$/, "$1");
|
|
3950
4021
|
const redirectTo = defaultLoginUrl + `?${new URLSearchParams({ return_to: cleanedPathname }).toString()}`;
|
|
3951
4022
|
return redirect(redirectTo);
|
|
@@ -3963,7 +4034,8 @@ function createCustomerAccountClient({
|
|
|
3963
4034
|
loginPath = "/account/login",
|
|
3964
4035
|
authorizePath = "/account/authorize",
|
|
3965
4036
|
defaultRedirectPath = "/account",
|
|
3966
|
-
language
|
|
4037
|
+
language,
|
|
4038
|
+
useCustomAuthDomain
|
|
3967
4039
|
}) {
|
|
3968
4040
|
if (customerApiVersion !== DEFAULT_CUSTOMER_API_VERSION) {
|
|
3969
4041
|
console.warn(
|
|
@@ -3980,7 +4052,6 @@ function createCustomerAccountClient({
|
|
|
3980
4052
|
"[h2:error:createCustomerAccountClient] The request object does not contain a URL."
|
|
3981
4053
|
);
|
|
3982
4054
|
}
|
|
3983
|
-
const authStatusHandler = customAuthStatusHandler ? customAuthStatusHandler : () => defaultAuthStatusHandler(request, loginPath);
|
|
3984
4055
|
const requestUrl = new URL(request.url);
|
|
3985
4056
|
const httpsOrigin = requestUrl.protocol === "http:" ? requestUrl.origin.replace("http", "https") : requestUrl.origin;
|
|
3986
4057
|
const redirectUri = ensureLocalRedirectUrl({
|
|
@@ -3988,6 +4059,11 @@ function createCustomerAccountClient({
|
|
|
3988
4059
|
defaultUrl: authorizePath,
|
|
3989
4060
|
redirectUrl: authUrl
|
|
3990
4061
|
});
|
|
4062
|
+
const ensureTunnel = (hostname) => checkTunnelDomain(hostname, useCustomAuthDomain, redirectUri);
|
|
4063
|
+
const authStatusHandler = customAuthStatusHandler ? customAuthStatusHandler : () => {
|
|
4064
|
+
ensureTunnel(requestUrl.hostname);
|
|
4065
|
+
return defaultAuthStatusHandler(request, loginPath);
|
|
4066
|
+
};
|
|
3991
4067
|
const getCustomerAccountUrl = createCustomerAccountHelper(
|
|
3992
4068
|
customerApiVersion,
|
|
3993
4069
|
shopId
|
|
@@ -4109,7 +4185,7 @@ function createCustomerAccountClient({
|
|
|
4109
4185
|
return session.get(CUSTOMER_ACCOUNT_SESSION_KEY)?.accessToken;
|
|
4110
4186
|
}
|
|
4111
4187
|
async function mutate(mutation, options) {
|
|
4112
|
-
|
|
4188
|
+
ensureTunnel(requestUrl.hostname);
|
|
4113
4189
|
ifInvalidCredentialThrowError();
|
|
4114
4190
|
mutation = minifyQuery(mutation);
|
|
4115
4191
|
assertMutation(mutation, "customer.mutate");
|
|
@@ -4119,7 +4195,7 @@ function createCustomerAccountClient({
|
|
|
4119
4195
|
);
|
|
4120
4196
|
}
|
|
4121
4197
|
async function query(query2, options) {
|
|
4122
|
-
|
|
4198
|
+
ensureTunnel(requestUrl.hostname);
|
|
4123
4199
|
ifInvalidCredentialThrowError();
|
|
4124
4200
|
query2 = minifyQuery(query2);
|
|
4125
4201
|
assertQuery(query2, "customer.query");
|
|
@@ -4143,7 +4219,7 @@ function createCustomerAccountClient({
|
|
|
4143
4219
|
return {
|
|
4144
4220
|
i18n: { language: language ?? "EN" },
|
|
4145
4221
|
login: async (options) => {
|
|
4146
|
-
|
|
4222
|
+
ensureTunnel(requestUrl.hostname);
|
|
4147
4223
|
ifInvalidCredentialThrowError();
|
|
4148
4224
|
const loginUrl = new URL(getCustomerAccountUrl("AUTH" /* AUTH */));
|
|
4149
4225
|
const state = generateState();
|
|
@@ -4195,7 +4271,7 @@ function createCustomerAccountClient({
|
|
|
4195
4271
|
return redirect(loginUrl.toString());
|
|
4196
4272
|
},
|
|
4197
4273
|
logout: async (options) => {
|
|
4198
|
-
|
|
4274
|
+
ensureTunnel(requestUrl.hostname);
|
|
4199
4275
|
ifInvalidCredentialThrowError();
|
|
4200
4276
|
const idToken = session.get(CUSTOMER_ACCOUNT_SESSION_KEY)?.idToken;
|
|
4201
4277
|
const postLogoutRedirectUri = ensureLocalRedirectUrl({
|
|
@@ -4230,7 +4306,7 @@ function createCustomerAccountClient({
|
|
|
4230
4306
|
mutate,
|
|
4231
4307
|
query,
|
|
4232
4308
|
authorize: async () => {
|
|
4233
|
-
|
|
4309
|
+
ensureTunnel(requestUrl.hostname);
|
|
4234
4310
|
ifInvalidCredentialThrowError();
|
|
4235
4311
|
const code = requestUrl.searchParams.get("code");
|
|
4236
4312
|
const state = requestUrl.searchParams.get("state");
|
|
@@ -4436,6 +4512,7 @@ function createHydrogenContext(options, additionalContext) {
|
|
|
4436
4512
|
customerApiVersion: customerAccountOptions?.apiVersion,
|
|
4437
4513
|
authUrl: customerAccountOptions?.authUrl,
|
|
4438
4514
|
customAuthStatusHandler: customerAccountOptions?.customAuthStatusHandler,
|
|
4515
|
+
useCustomAuthDomain: customerAccountOptions?.useCustomAuthDomain,
|
|
4439
4516
|
// locale - i18n.language is a union of StorefrontLanguageCode | CustomerLanguageCode
|
|
4440
4517
|
// We cast here because createCustomerAccountClient expects CustomerLanguageCode specifically,
|
|
4441
4518
|
// but the union type is compatible since most language codes overlap between the two APIs
|
|
@@ -4514,8 +4591,7 @@ function createRequestHandler({
|
|
|
4514
4591
|
mode,
|
|
4515
4592
|
poweredByHeader = true,
|
|
4516
4593
|
getLoadContext,
|
|
4517
|
-
collectTrackingInformation = true
|
|
4518
|
-
proxyStandardRoutes = true
|
|
4594
|
+
collectTrackingInformation = true
|
|
4519
4595
|
}) {
|
|
4520
4596
|
const handleRequest = reactRouter.createRequestHandler(build, mode);
|
|
4521
4597
|
const appendPoweredByHeader = poweredByHeader ? (response) => response.headers.append("powered-by", "Shopify, Hydrogen") : void 0;
|
|
@@ -4537,27 +4613,28 @@ function createRequestHandler({
|
|
|
4537
4613
|
}
|
|
4538
4614
|
const context = await getLoadContext?.(request);
|
|
4539
4615
|
const storefront = context?.storefront || context?.get?.(storefrontContext);
|
|
4540
|
-
if (
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4616
|
+
if (!storefront) {
|
|
4617
|
+
throw new Error(
|
|
4618
|
+
"[h2:createRequestHandler] Storefront instance is required in the load context. Make sure to use createHydrogenContext() or provide a storefront instance via getLoadContext."
|
|
4619
|
+
);
|
|
4620
|
+
}
|
|
4621
|
+
if (storefront.isStorefrontApiUrl(request)) {
|
|
4622
|
+
const response2 = await storefront.forward(request);
|
|
4623
|
+
appendPoweredByHeader?.(response2);
|
|
4624
|
+
return response2;
|
|
4625
|
+
}
|
|
4626
|
+
if (storefront.isMcpUrl(request)) {
|
|
4627
|
+
const response2 = await storefront.forwardMcp(request);
|
|
4628
|
+
appendPoweredByHeader?.(response2);
|
|
4629
|
+
return response2;
|
|
4551
4630
|
}
|
|
4552
4631
|
const response = await handleRequest(request, context);
|
|
4553
|
-
if (
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
appendServerTimingHeader(response, { [HYDROGEN_SFAPI_PROXY_KEY]: "1" });
|
|
4560
|
-
}
|
|
4632
|
+
if (collectTrackingInformation) {
|
|
4633
|
+
storefront.setCollectedSubrequestHeaders(response);
|
|
4634
|
+
}
|
|
4635
|
+
const fetchDest = request.headers.get("sec-fetch-dest");
|
|
4636
|
+
if (fetchDest && fetchDest === "document" || request.headers.get("accept")?.includes("text/html")) {
|
|
4637
|
+
appendServerTimingHeader(response, { [HYDROGEN_SFAPI_PROXY_KEY]: "1" });
|
|
4561
4638
|
}
|
|
4562
4639
|
appendPoweredByHeader?.(response);
|
|
4563
4640
|
return response;
|
|
@@ -6597,14 +6674,14 @@ var QUERIES = {
|
|
|
6597
6674
|
//! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartNoteUpdate
|
|
6598
6675
|
//! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartSelectedDeliveryOptionsUpdate
|
|
6599
6676
|
//! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartMetafieldsSet
|
|
6600
|
-
//! @see https://shopify.dev/docs/api/storefront/2026-
|
|
6677
|
+
//! @see https://shopify.dev/docs/api/storefront/2026-04/mutations/cartMetafieldDelete
|
|
6601
6678
|
//! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartGiftCardCodesUpdate
|
|
6602
6679
|
//! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartGiftCardCodesAdd
|
|
6603
6680
|
//! @see https://shopify.dev/docs/api/storefront/latest/mutations/cartGiftCardCodesRemove
|
|
6604
6681
|
//! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartDeliveryAddressesAdd
|
|
6605
6682
|
//! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartDeliveryAddressesRemove
|
|
6606
6683
|
//! @see: https://shopify.dev/docs/api/storefront/latest/mutations/cartDeliveryAddressesUpdate
|
|
6607
|
-
//! @see: https://shopify.dev/docs/api/storefront/2026-
|
|
6684
|
+
//! @see: https://shopify.dev/docs/api/storefront/2026-04/mutations/cartDeliveryAddressesReplace
|
|
6608
6685
|
|
|
6609
6686
|
Object.defineProperty(exports, "AnalyticsEventName", {
|
|
6610
6687
|
enumerable: true,
|