@decocms/apps-vtex 8.1.0-next.0 → 8.1.0-next.2

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 (163) hide show
  1. package/dist/index.d.ts +6 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +6 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/utils/constants.d.ts +28 -0
  6. package/dist/utils/constants.d.ts.map +1 -0
  7. package/dist/utils/constants.js +17 -0
  8. package/dist/utils/constants.js.map +1 -0
  9. package/dist/utils/cookieSanitizer.d.ts +87 -0
  10. package/dist/utils/cookieSanitizer.d.ts.map +1 -0
  11. package/dist/utils/cookieSanitizer.js +136 -0
  12. package/dist/utils/cookieSanitizer.js.map +1 -0
  13. package/dist/utils/operationRouter.d.ts +25 -0
  14. package/dist/utils/operationRouter.d.ts.map +1 -0
  15. package/dist/utils/operationRouter.js +120 -0
  16. package/dist/utils/operationRouter.js.map +1 -0
  17. package/dist/utils/types.d.ts +944 -0
  18. package/dist/utils/types.d.ts.map +1 -0
  19. package/dist/utils/types.js +25 -0
  20. package/dist/utils/types.js.map +1 -0
  21. package/dist/vtexClient.d.ts +173 -0
  22. package/dist/vtexClient.d.ts.map +1 -0
  23. package/dist/vtexClient.js +242 -0
  24. package/dist/vtexClient.js.map +1 -0
  25. package/package.json +16 -57
  26. package/src/index.ts +11 -29
  27. package/src/utils/constants.ts +2 -56
  28. package/src/utils/cookieSanitizer.ts +59 -59
  29. package/src/utils/operationRouter.ts +75 -65
  30. package/src/utils/types.ts +709 -1557
  31. package/src/vtexClient.ts +358 -347
  32. package/src/README.md +0 -6
  33. package/src/__conformance__/upstream.test.ts +0 -158
  34. package/src/__tests__/client-segment-cookie.test.ts +0 -255
  35. package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
  36. package/src/__tests__/schemas.test.ts +0 -71
  37. package/src/actions/address.ts +0 -250
  38. package/src/actions/analytics/sendEvent.ts +0 -86
  39. package/src/actions/auth.ts +0 -333
  40. package/src/actions/checkout.ts +0 -659
  41. package/src/actions/index.ts +0 -11
  42. package/src/actions/masterData.ts +0 -168
  43. package/src/actions/misc.ts +0 -188
  44. package/src/actions/newsletter.ts +0 -105
  45. package/src/actions/orders.ts +0 -34
  46. package/src/actions/profile.ts +0 -201
  47. package/src/actions/session.ts +0 -85
  48. package/src/actions/trigger.ts +0 -43
  49. package/src/actions/wishlist.ts +0 -114
  50. package/src/client.ts +0 -724
  51. package/src/commerceLoaders.ts +0 -267
  52. package/src/hooks/__tests__/createCart.render.test.ts +0 -114
  53. package/src/hooks/__tests__/createCart.test.ts +0 -92
  54. package/src/hooks/__tests__/createUseCart.test.ts +0 -184
  55. package/src/hooks/__tests__/createUseUser.test.ts +0 -48
  56. package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
  57. package/src/hooks/cartQuery.ts +0 -204
  58. package/src/hooks/createCart.ts +0 -427
  59. package/src/hooks/createUseCart.ts +0 -360
  60. package/src/hooks/createUseUser.ts +0 -153
  61. package/src/hooks/createUseWishlist.ts +0 -242
  62. package/src/hooks/index.ts +0 -25
  63. package/src/hooks/useAutocomplete.ts +0 -83
  64. package/src/hooks/useCart.ts +0 -243
  65. package/src/hooks/useUser.ts +0 -78
  66. package/src/hooks/useWishlist.ts +0 -119
  67. package/src/invoke.ts +0 -235
  68. package/src/loaders/ProductDetailsPage.ts +0 -1
  69. package/src/loaders/ProductList.ts +0 -1
  70. package/src/loaders/ProductListingPage.ts +0 -1
  71. package/src/loaders/__tests__/legacyProductList.test.ts +0 -157
  72. package/src/loaders/address.ts +0 -115
  73. package/src/loaders/autocomplete.ts +0 -56
  74. package/src/loaders/brands.ts +0 -44
  75. package/src/loaders/cart/attachments.ts +0 -53
  76. package/src/loaders/cart/full.ts +0 -67
  77. package/src/loaders/cart/gifts.ts +0 -43
  78. package/src/loaders/cart/orderFormId.ts +0 -31
  79. package/src/loaders/cart/shipping.test.ts +0 -125
  80. package/src/loaders/cart/shipping.ts +0 -136
  81. package/src/loaders/cart/summary.ts +0 -36
  82. package/src/loaders/cart.ts +0 -52
  83. package/src/loaders/catalog.ts +0 -163
  84. package/src/loaders/collections.ts +0 -55
  85. package/src/loaders/index.ts +0 -20
  86. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
  87. package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -111
  88. package/src/loaders/intelligentSearch/productList.ts +0 -170
  89. package/src/loaders/intelligentSearch/productListingPage.ts +0 -525
  90. package/src/loaders/intelligentSearch/suggestions.ts +0 -44
  91. package/src/loaders/legacy/productDetailsPage.ts +0 -1
  92. package/src/loaders/legacy/productList.ts +0 -1
  93. package/src/loaders/legacy/relatedProductsLoader.ts +0 -79
  94. package/src/loaders/legacy.ts +0 -710
  95. package/src/loaders/logistics.ts +0 -111
  96. package/src/loaders/minicart.ts +0 -78
  97. package/src/loaders/navbar.ts +0 -26
  98. package/src/loaders/orders.ts +0 -92
  99. package/src/loaders/pageType.ts +0 -68
  100. package/src/loaders/paths/PDPDefaultPath.ts +0 -51
  101. package/src/loaders/payment.ts +0 -97
  102. package/src/loaders/productListFull.ts +0 -157
  103. package/src/loaders/profile.ts +0 -138
  104. package/src/loaders/promotion.ts +0 -30
  105. package/src/loaders/search.ts +0 -124
  106. package/src/loaders/session.ts +0 -83
  107. package/src/loaders/user.ts +0 -87
  108. package/src/loaders/wishlist.ts +0 -89
  109. package/src/loaders/wishlistProducts.ts +0 -69
  110. package/src/loaders/workflow/products.ts +0 -62
  111. package/src/loaders/workflow.ts +0 -316
  112. package/src/logo.png +0 -0
  113. package/src/manifest.gen.ts +0 -89
  114. package/src/middleware.cacheHeaders.test.ts +0 -44
  115. package/src/middleware.ts +0 -240
  116. package/src/mod.ts +0 -173
  117. package/src/registry.ts +0 -9
  118. package/src/schemas.gen.ts +0 -3191
  119. package/src/schemas.ts +0 -30
  120. package/src/types.ts +0 -248
  121. package/src/utils/__tests__/cartProjection.test.ts +0 -118
  122. package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
  123. package/src/utils/__tests__/fetch.test.ts +0 -80
  124. package/src/utils/__tests__/fetchCache.test.ts +0 -155
  125. package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
  126. package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
  127. package/src/utils/__tests__/minicart.test.ts +0 -184
  128. package/src/utils/__tests__/operationRouter.test.ts +0 -227
  129. package/src/utils/__tests__/resilience.test.ts +0 -205
  130. package/src/utils/__tests__/resourceRange.test.ts +0 -32
  131. package/src/utils/__tests__/simulationCache.test.ts +0 -40
  132. package/src/utils/__tests__/sitemap.test.ts +0 -185
  133. package/src/utils/__tests__/slugify.test.ts +0 -31
  134. package/src/utils/__tests__/storefrontBaseUrl.test.ts +0 -61
  135. package/src/utils/__tests__/transform.test.ts +0 -1111
  136. package/src/utils/accountLoaders.ts +0 -203
  137. package/src/utils/authHelpers.ts +0 -85
  138. package/src/utils/batch.ts +0 -18
  139. package/src/utils/buildOfferShelf.test.ts +0 -59
  140. package/src/utils/cartProjection.ts +0 -96
  141. package/src/utils/cookies.ts +0 -167
  142. package/src/utils/enrichment.ts +0 -556
  143. package/src/utils/fetch.ts +0 -111
  144. package/src/utils/fetchCache.ts +0 -55
  145. package/src/utils/index.ts +0 -19
  146. package/src/utils/instrumentedFetch.ts +0 -98
  147. package/src/utils/intelligentSearch.ts +0 -96
  148. package/src/utils/legacy.ts +0 -139
  149. package/src/utils/minicart.ts +0 -139
  150. package/src/utils/pickAndOmit.ts +0 -25
  151. package/src/utils/proxy.ts +0 -495
  152. package/src/utils/resilience.ts +0 -352
  153. package/src/utils/resourceRange.ts +0 -10
  154. package/src/utils/segment.ts +0 -152
  155. package/src/utils/similars.ts +0 -35
  156. package/src/utils/simulationCache.ts +0 -73
  157. package/src/utils/sitemap.ts +0 -283
  158. package/src/utils/slugCache.ts +0 -28
  159. package/src/utils/slugify.ts +0 -13
  160. package/src/utils/transform.ts +0 -1691
  161. package/src/utils/vtexId.ts +0 -151
  162. package/src/vtexClient.test.ts +0 -204
  163. package/tsconfig.json +0 -7
@@ -1,283 +0,0 @@
1
- /**
2
- * VTEX Sitemap utilities.
3
- *
4
- * Two flavors:
5
- * - `getVtexSitemapEntries()` — flatten VTEX sub-sitemaps into a single
6
- * `SitemapEntry[]` list, for composition with the CMS sitemap generator.
7
- * - `createVtexSitemapProxy()` — proxy `/sitemap.xml` and `/sitemap/*`
8
- * straight from VTEX's commerce-stable origin, preserving the sitemap-index
9
- * shape (so crawlers stay within Google's per-file size limit). This is the
10
- * right choice when the storefront has no native sitemap renderer and just
11
- * needs to expose VTEX's existing crawl tree to the public hostname.
12
- */
13
-
14
- import { type FetchFn, withFetchTimeout } from "@decocms/blocks/sdk/fetchTimeout";
15
- import { getVtexConfig, vtexFetchResponse, vtexHost } from "../client";
16
-
17
- export interface SitemapEntry {
18
- loc: string;
19
- lastmod?: string;
20
- changefreq?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
21
- priority?: number;
22
- }
23
-
24
- /**
25
- * Fetch sitemap entries from VTEX's sitemap API.
26
- *
27
- * VTEX exposes /sitemap.xml which contains links to sub-sitemaps
28
- * (products, categories, brands, etc.). This function fetches the
29
- * main sitemap index and extracts all <loc> entries from the
30
- * referenced sub-sitemaps.
31
- *
32
- * @param origin - The storefront origin (e.g., "https://www.mystore.com")
33
- * @param options.maxDepth - How many levels of sub-sitemaps to follow (default: 1)
34
- * @param options.rewriteHost - Whether to rewrite VTEX hostnames to the storefront origin (default: true)
35
- */
36
- export async function getVtexSitemapEntries(
37
- origin: string,
38
- options?: {
39
- maxDepth?: number;
40
- rewriteHost?: boolean;
41
- includeBrands?: boolean;
42
- includeCategories?: boolean;
43
- includeProducts?: boolean;
44
- },
45
- ): Promise<SitemapEntry[]> {
46
- const config = getVtexConfig();
47
- const vtexSitemapHost = vtexHost("vtexcommercestable", config);
48
- const rewrite = options?.rewriteHost !== false;
49
- const includeProducts = options?.includeProducts !== false;
50
- const includeCategories = options?.includeCategories !== false;
51
- const includeBrands = options?.includeBrands !== false;
52
-
53
- try {
54
- const mainSitemapUrl = `https://${vtexSitemapHost}/sitemap.xml`;
55
- const mainResponse = await vtexFetchResponse(mainSitemapUrl);
56
- const mainXml = await mainResponse.text();
57
-
58
- const subSitemapUrls = extractLocs(mainXml);
59
- const entries: SitemapEntry[] = [];
60
-
61
- const filteredUrls = subSitemapUrls.filter((url) => {
62
- const lower = url.toLowerCase();
63
- if (!includeProducts && lower.includes("product")) return false;
64
- if (!includeCategories && lower.includes("categor")) return false;
65
- if (!includeBrands && lower.includes("brand")) return false;
66
- return true;
67
- });
68
-
69
- const maxDepth = options?.maxDepth ?? 1;
70
- if (maxDepth < 1) {
71
- return filteredUrls.map((url) => ({
72
- loc: rewrite ? rewriteUrl(url, vtexSitemapHost, origin) : url,
73
- changefreq: "daily" as const,
74
- priority: 0.5,
75
- }));
76
- }
77
-
78
- const settled = await Promise.allSettled(
79
- filteredUrls.map(async (subUrl) => {
80
- try {
81
- const resp = await vtexFetchResponse(subUrl);
82
- const xml = await resp.text();
83
- return extractLocs(xml);
84
- } catch {
85
- return [];
86
- }
87
- }),
88
- );
89
-
90
- const today = new Date().toISOString().split("T")[0];
91
-
92
- for (const result of settled) {
93
- if (result.status !== "fulfilled") continue;
94
- for (const loc of result.value) {
95
- entries.push({
96
- loc: rewrite ? rewriteUrl(loc, vtexSitemapHost, origin) : loc,
97
- lastmod: today,
98
- changefreq: "daily",
99
- priority: 0.5,
100
- });
101
- }
102
- }
103
-
104
- return entries;
105
- } catch (error) {
106
- console.error("[VTEX Sitemap] Failed to fetch VTEX sitemap:", error);
107
- return [];
108
- }
109
- }
110
-
111
- function extractLocs(xml: string): string[] {
112
- const locs: string[] = [];
113
- const regex = /<loc>\s*(.*?)\s*<\/loc>/g;
114
- let match: RegExpExecArray | null;
115
- while ((match = regex.exec(xml)) !== null) {
116
- if (match[1]) locs.push(match[1].trim());
117
- }
118
- return locs;
119
- }
120
-
121
- function rewriteUrl(url: string, vtexSitemapHost: string, origin: string): string {
122
- try {
123
- const parsed = new URL(url);
124
- const originParsed = new URL(origin);
125
- const config = getVtexConfig();
126
- const domain = config.domain ?? "com.br";
127
- if (
128
- parsed.hostname === vtexSitemapHost ||
129
- parsed.hostname.endsWith(`.vtexcommercestable.${domain}`)
130
- ) {
131
- parsed.protocol = originParsed.protocol;
132
- parsed.hostname = originParsed.hostname;
133
- parsed.port = originParsed.port;
134
- }
135
- return parsed.toString();
136
- } catch {
137
- return url.replace(`https://${vtexSitemapHost}`, origin);
138
- }
139
- }
140
-
141
- // ---------------------------------------------------------------------------
142
- // VTEX sitemap proxy factory
143
- // ---------------------------------------------------------------------------
144
-
145
- /**
146
- * Returns true if `pathname` is one of the proxied sitemap paths
147
- * (`/sitemap.xml` or any `/sitemap/*` sub-sitemap).
148
- */
149
- export function isVtexSitemapPath(pathname: string): boolean {
150
- return pathname === "/sitemap.xml" || pathname.startsWith("/sitemap/");
151
- }
152
-
153
- export interface VtexSitemapProxyConfig {
154
- /**
155
- * Extra `<sitemap>` entries to inject into the root sitemap index
156
- * (`/sitemap.xml` only — sub-sitemaps are passed through untouched).
157
- *
158
- * Useful for site-managed sitemaps such as a static search-result
159
- * index (`sitemap-busca.xml`) that VTEX doesn't generate.
160
- *
161
- * Each value is normalized to an absolute URL on the storefront
162
- * origin: leading-slash paths become `${origin}${path}`, and bare
163
- * names become `${origin}/${name}`. Absolute URLs are used as-is.
164
- *
165
- * @example ["/sitemap-busca.xml"]
166
- */
167
- extraSitemaps?: string[];
168
-
169
- /**
170
- * VTEX environment for the upstream sitemap fetch.
171
- * @default "vtexcommercestable"
172
- */
173
- environment?: "vtexcommercestable" | "vtexcommercebeta";
174
-
175
- /**
176
- * `Cache-Control` header to set on proxied responses. The default
177
- * favors edge caching (Cloudflare honors `s-maxage`) with a long
178
- * stale-while-revalidate window so a slow VTEX origin never blocks
179
- * crawlers.
180
- *
181
- * @default "public, s-maxage=3600, stale-while-revalidate=86400"
182
- */
183
- cacheControl?: string;
184
-
185
- /**
186
- * Optional fetch override — primarily for tests. Defaults to the
187
- * platform `fetch`.
188
- */
189
- fetchImpl?: FetchFn;
190
- }
191
-
192
- const DEFAULT_SITEMAP_CACHE_CONTROL = "public, s-maxage=3600, stale-while-revalidate=86400";
193
-
194
- function normalizeExtraSitemap(entry: string, origin: string): string {
195
- if (entry.startsWith("http://") || entry.startsWith("https://")) return entry;
196
- const path = entry.startsWith("/") ? entry : `/${entry}`;
197
- return `${origin}${path}`;
198
- }
199
-
200
- /**
201
- * Creates a sitemap proxy handler that mirrors VTEX's `/sitemap.xml`
202
- * (and sub-sitemaps) onto the storefront origin.
203
- *
204
- * Returns a function compatible with `createDecoWorkerEntry`'s
205
- * `proxyHandler`: it returns `null` for non-sitemap paths, so it
206
- * composes naturally with other proxy handlers
207
- * (`createVtexCheckoutProxy`, custom logic, etc.).
208
- *
209
- * The VTEX account is read from the `configureVtex(...)` call done at
210
- * worker startup — no per-call account configuration is needed.
211
- *
212
- * @example
213
- * ```ts
214
- * import { createVtexSitemapProxy } from "@decocms/apps/vtex/utils/sitemap";
215
- * import {
216
- * createVtexCheckoutProxy,
217
- * shouldProxyToVtex,
218
- * } from "@decocms/apps/vtex/utils/proxy";
219
- *
220
- * const proxySitemap = createVtexSitemapProxy({
221
- * extraSitemaps: ["/sitemap-busca.xml"], // optional, site-managed
222
- * });
223
- * const proxyCheckout = createVtexCheckoutProxy({ ... });
224
- *
225
- * createDecoWorkerEntry(serverEntry, {
226
- * proxyHandler: async (request, url) => {
227
- * const sitemap = await proxySitemap(request, url);
228
- * if (sitemap) return sitemap;
229
- *
230
- * if (!shouldProxyToVtex(url.pathname)) return null;
231
- * return proxyCheckout(request, url);
232
- * },
233
- * });
234
- * ```
235
- */
236
- export function createVtexSitemapProxy(
237
- config: VtexSitemapProxyConfig = {},
238
- ): (request: Request, url: URL) => Promise<Response | null> {
239
- const environment = config.environment ?? "vtexcommercestable";
240
- const cacheControl = config.cacheControl ?? DEFAULT_SITEMAP_CACHE_CONTROL;
241
- const extraSitemaps = config.extraSitemaps ?? [];
242
- const fetchImpl = config.fetchImpl ?? withFetchTimeout();
243
-
244
- return async (_request: Request, url: URL): Promise<Response | null> => {
245
- if (!isVtexSitemapPath(url.pathname)) return null;
246
-
247
- // vtexHost() reads the configured account from configureVtex().
248
- const vtexSitemapHost = vtexHost(environment);
249
- const target = `https://${vtexSitemapHost}${url.pathname}`;
250
-
251
- try {
252
- const resp = await fetchImpl(target);
253
- if (!resp.ok) {
254
- console.error(`[vtex-sitemap] VTEX returned ${resp.status} for ${url.pathname}`);
255
- return new Response("Sitemap temporarily unavailable", { status: 502 });
256
- }
257
-
258
- let xml = await resp.text();
259
- xml = xml.replaceAll(`https://${vtexSitemapHost}`, url.origin);
260
-
261
- if (url.pathname === "/sitemap.xml" && extraSitemaps.length > 0) {
262
- const extraEntries = extraSitemaps
263
- .map(
264
- (s) =>
265
- ` <sitemap>\n <loc>${normalizeExtraSitemap(s, url.origin)}</loc>\n </sitemap>`,
266
- )
267
- .join("\n");
268
- xml = xml.replace("</sitemapindex>", `${extraEntries}\n</sitemapindex>`);
269
- }
270
-
271
- return new Response(xml, {
272
- status: 200,
273
- headers: {
274
- "Content-Type": "application/xml; charset=utf-8",
275
- "Cache-Control": cacheControl,
276
- },
277
- });
278
- } catch (err) {
279
- console.error("[vtex-sitemap] Failed to proxy VTEX sitemap:", err);
280
- return new Response("Sitemap temporarily unavailable", { status: 502 });
281
- }
282
- };
283
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * In-flight dedup + SWR cache for VTEX Legacy Catalog slug→product lookups.
3
- *
4
- * Multiple loaders on the same page (PDP, relatedProducts x3, BuyTogether)
5
- * all call `/api/catalog_system/pub/products/search/{slug}/p` for the same slug.
6
- * This module routes through vtexCachedFetch which provides in-flight dedup
7
- * and stale-while-revalidate caching (3 min TTL for 200 responses).
8
- */
9
- import { getVtexConfig, vtexCachedFetch } from "../client";
10
- import type { LegacyProduct } from "./types";
11
-
12
- export function searchBySlug(linkText: string): Promise<LegacyProduct[] | null> {
13
- const config = getVtexConfig();
14
- const sc = config.salesChannel;
15
- const scParam = sc ? `?sc=${sc}` : "";
16
-
17
- return vtexCachedFetch<LegacyProduct[]>(
18
- `/api/catalog_system/pub/products/search/${encodeURIComponent(linkText)}/p${scParam}`,
19
- ).catch((err) => {
20
- console.error(`[VTEX] searchBySlug error for "${linkText}":`, err);
21
- return null;
22
- });
23
- }
24
-
25
- export async function resolveProductIdBySlug(linkText: string): Promise<string | null> {
26
- const products = await searchBySlug(linkText);
27
- return products?.length ? products[0].productId : null;
28
- }
@@ -1,13 +0,0 @@
1
- const mapped = JSON.parse(
2
- `{"Á":"A","Ä":"A","Â":"A","À":"A","Ã":"A","Å":"A","Č":"C","Ç":"C","Ć":"C","Ď":"D","É":"E","Ě":"E","Ë":"E","È":"E","Ê":"E","Ẽ":"E","Ĕ":"E","Ȇ":"E","Í":"I","Ì":"I","Î":"I","Ï":"I","Ň":"N","Ñ":"N","Ó":"O","Ö":"O","Ò":"O","Ô":"O","Õ":"O","Ø":"O","Ř":"R","Ŕ":"R","Š":"S","Ť":"T","Ú":"U","Ů":"U","Ü":"U","Ù":"U","Û":"U","Ý":"Y","Ÿ":"Y","Ž":"Z","á":"a","ä":"a","â":"a","à":"a","ã":"a","å":"a","č":"c","ç":"c","ć":"c","ď":"d","é":"e","ě":"e","ë":"e","è":"e","ê":"e","ẽ":"e","ĕ":"e","ȇ":"e","í":"i","ì":"i","î":"i","ï":"i","ň":"n","ñ":"n","ó":"o","ö":"o","ò":"o","ô":"o","õ":"o","ø":"o","ð":"o","ř":"r","ŕ":"r","š":"s","ť":"t","ú":"u","ů":"u","ü":"u","ù":"u","û":"u","ý":"y","ÿ":"y","ž":"z","þ":"b","Þ":"B","Đ":"D","đ":"d","ß":"B","Æ":"A","a":"a"}`,
3
- );
4
-
5
- export const slugify = (str: string) =>
6
- str
7
- .replace(/,/g, "")
8
- .replace(/[·/_,:]/g, "-")
9
- .replace(/[*+~.()'"!:@&[\]`/ %$#?{}|><=_^]/g, "-")
10
- .split("")
11
- .map((char) => mapped[char] ?? char)
12
- .join("")
13
- .toLowerCase();