@jay-framework/wix-stores 0.15.0 → 0.15.4
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/README.md +157 -96
- package/dist/contracts/category-page.jay-contract.d.ts +10 -1
- package/dist/contracts/product-card.jay-contract +8 -2
- package/dist/contracts/product-card.jay-contract.d.ts +17 -4
- package/dist/contracts/product-options.jay-contract +0 -5
- package/dist/contracts/product-options.jay-contract.d.ts +1 -2
- package/dist/contracts/product-search.jay-contract +71 -0
- package/dist/contracts/product-search.jay-contract.d.ts +57 -3
- package/dist/index.client.js +180 -21
- package/dist/index.d.ts +142 -75
- package/dist/index.js +429 -137
- package/package.json +14 -14
package/dist/index.js
CHANGED
|
@@ -44,13 +44,49 @@ function getInventoryClient(wixClient) {
|
|
|
44
44
|
}
|
|
45
45
|
const WIX_STORES_SERVICE_MARKER = createJayService("Wix Store Service");
|
|
46
46
|
function provideWixStoresService(wixClient, options) {
|
|
47
|
+
let cachedTree = null;
|
|
48
|
+
const categoriesClient = getCategoriesClient(wixClient);
|
|
47
49
|
const service = {
|
|
48
50
|
products: getProductsV3Client(wixClient),
|
|
49
|
-
categories:
|
|
51
|
+
categories: categoriesClient,
|
|
50
52
|
inventory: getInventoryClient(wixClient),
|
|
51
|
-
// Keep cart for backward compatibility, but prefer WIX_CART_SERVICE
|
|
52
53
|
cart: getCurrentCartClient(wixClient),
|
|
53
|
-
|
|
54
|
+
urls: options?.urls ?? { product: "/products/{slug}", category: null },
|
|
55
|
+
defaultCategory: options?.defaultCategory ?? null,
|
|
56
|
+
async getCategoryTree() {
|
|
57
|
+
if (cachedTree) return cachedTree;
|
|
58
|
+
const slugMap = /* @__PURE__ */ new Map();
|
|
59
|
+
const parentMap = /* @__PURE__ */ new Map();
|
|
60
|
+
const rootIds = /* @__PURE__ */ new Set();
|
|
61
|
+
const imageMap = /* @__PURE__ */ new Map();
|
|
62
|
+
try {
|
|
63
|
+
const processItems = (items) => {
|
|
64
|
+
for (const cat of items) {
|
|
65
|
+
if (!cat._id || !cat.slug) continue;
|
|
66
|
+
slugMap.set(cat._id, cat.slug);
|
|
67
|
+
if (cat.parentCategory?._id) {
|
|
68
|
+
parentMap.set(cat._id, cat.parentCategory._id);
|
|
69
|
+
} else {
|
|
70
|
+
rootIds.add(cat._id);
|
|
71
|
+
}
|
|
72
|
+
const imageUrl = cat.media?.mainMedia?.image?.url || cat.media?.mainMedia?.url;
|
|
73
|
+
if (imageUrl) {
|
|
74
|
+
imageMap.set(cat._id, imageUrl);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
let result = await categoriesClient.queryCategories({ treeReference: { appNamespace: "@wix/stores" } }).eq("visible", true).limit(100).find();
|
|
79
|
+
processItems(result.items || []);
|
|
80
|
+
while (result.hasNext()) {
|
|
81
|
+
result = await result.next();
|
|
82
|
+
processItems(result.items || []);
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error("[wix-stores] Failed to build category tree:", error);
|
|
86
|
+
}
|
|
87
|
+
cachedTree = { slugMap, parentMap, rootIds, imageMap };
|
|
88
|
+
return cachedTree;
|
|
89
|
+
}
|
|
54
90
|
};
|
|
55
91
|
registerService(WIX_STORES_SERVICE_MARKER, service);
|
|
56
92
|
return service;
|
|
@@ -80,7 +116,8 @@ var ProductType$1 = /* @__PURE__ */ ((ProductType2) => {
|
|
|
80
116
|
var QuickAddType = /* @__PURE__ */ ((QuickAddType2) => {
|
|
81
117
|
QuickAddType2[QuickAddType2["SIMPLE"] = 0] = "SIMPLE";
|
|
82
118
|
QuickAddType2[QuickAddType2["SINGLE_OPTION"] = 1] = "SINGLE_OPTION";
|
|
83
|
-
QuickAddType2[QuickAddType2["
|
|
119
|
+
QuickAddType2[QuickAddType2["COLOR_AND_TEXT_OPTIONS"] = 2] = "COLOR_AND_TEXT_OPTIONS";
|
|
120
|
+
QuickAddType2[QuickAddType2["NEEDS_CONFIGURATION"] = 3] = "NEEDS_CONFIGURATION";
|
|
84
121
|
return QuickAddType2;
|
|
85
122
|
})(QuickAddType || {});
|
|
86
123
|
var OptionRenderType$1 = /* @__PURE__ */ ((OptionRenderType2) => {
|
|
@@ -136,33 +173,54 @@ function formatWixMediaUrl(_id, url, resize) {
|
|
|
136
173
|
}
|
|
137
174
|
return "";
|
|
138
175
|
}
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
product.allCategoriesInfo.categories.map((c) => c._id).filter(Boolean)
|
|
145
|
-
);
|
|
146
|
-
for (const { categoryId, prefix } of prefixConfig) {
|
|
147
|
-
if (productCategoryIds.has(categoryId)) {
|
|
148
|
-
return prefix;
|
|
176
|
+
function findRootCategoryId(categoryId, tree) {
|
|
177
|
+
let current = categoryId;
|
|
178
|
+
for (let depth = 0; depth < 20; depth++) {
|
|
179
|
+
if (tree.rootIds.has(current)) {
|
|
180
|
+
return current;
|
|
149
181
|
}
|
|
182
|
+
const parentId = tree.parentMap.get(current);
|
|
183
|
+
if (!parentId) return current;
|
|
184
|
+
current = parentId;
|
|
150
185
|
}
|
|
151
|
-
return
|
|
186
|
+
return current;
|
|
152
187
|
}
|
|
153
|
-
function
|
|
154
|
-
|
|
155
|
-
|
|
188
|
+
function findRootCategorySlug(categoryId, tree) {
|
|
189
|
+
const rootId = findRootCategoryId(categoryId, tree);
|
|
190
|
+
return tree.slugMap.get(rootId) ?? "";
|
|
191
|
+
}
|
|
192
|
+
function findCategoryImage(categoryId, tree) {
|
|
193
|
+
let current = categoryId;
|
|
194
|
+
for (let depth = 0; depth < 20 && current; depth++) {
|
|
195
|
+
const image = tree.imageMap.get(current);
|
|
196
|
+
if (image) return image;
|
|
197
|
+
current = tree.parentMap.get(current);
|
|
156
198
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
199
|
+
return "";
|
|
200
|
+
}
|
|
201
|
+
function buildProductUrl(urls, tree, slug, mainCategoryId) {
|
|
202
|
+
let url = urls.product;
|
|
203
|
+
url = url.replace("{slug}", slug);
|
|
204
|
+
if (url.includes("{category}")) {
|
|
205
|
+
const categorySlug = tree.slugMap.get(mainCategoryId);
|
|
206
|
+
url = url.replace("{category}", categorySlug);
|
|
164
207
|
}
|
|
165
|
-
|
|
208
|
+
if (url.includes("{prefix}")) {
|
|
209
|
+
const prefixSlug = findRootCategorySlug(mainCategoryId, tree);
|
|
210
|
+
url = url.replace("{prefix}", prefixSlug);
|
|
211
|
+
}
|
|
212
|
+
return url;
|
|
213
|
+
}
|
|
214
|
+
function buildCategoryUrl(urls, tree, categorySlug, categoryId) {
|
|
215
|
+
if (!urls.category) return null;
|
|
216
|
+
let url = urls.category;
|
|
217
|
+
url = url.replace("{category}", categorySlug);
|
|
218
|
+
if (url.includes("{prefix}")) {
|
|
219
|
+
const prefixSlug = findRootCategorySlug(categoryId, tree);
|
|
220
|
+
if (!prefixSlug) return null;
|
|
221
|
+
url = url.replace("{prefix}", prefixSlug);
|
|
222
|
+
}
|
|
223
|
+
return url.includes("{") ? null : url;
|
|
166
224
|
}
|
|
167
225
|
function mapAvailabilityStatus(status) {
|
|
168
226
|
switch (status) {
|
|
@@ -198,7 +256,15 @@ function isValidPrice(amount) {
|
|
|
198
256
|
function getQuickAddType(product) {
|
|
199
257
|
const optionCount = product.options?.length ?? 0;
|
|
200
258
|
const hasModifiers = (product.modifiers?.length ?? 0) > 0;
|
|
201
|
-
if (hasModifiers || optionCount >
|
|
259
|
+
if (hasModifiers || optionCount > 2) {
|
|
260
|
+
return QuickAddType.NEEDS_CONFIGURATION;
|
|
261
|
+
}
|
|
262
|
+
if (optionCount === 2) {
|
|
263
|
+
const hasColor = product.options.some((o) => o.optionRenderType === "SWATCH_CHOICES");
|
|
264
|
+
const hasText = product.options.some((o) => o.optionRenderType === "TEXT_CHOICES");
|
|
265
|
+
if (hasColor && hasText) {
|
|
266
|
+
return QuickAddType.COLOR_AND_TEXT_OPTIONS;
|
|
267
|
+
}
|
|
202
268
|
return QuickAddType.NEEDS_CONFIGURATION;
|
|
203
269
|
}
|
|
204
270
|
if (optionCount === 1) {
|
|
@@ -214,31 +280,85 @@ function mapChoiceType(choiceType) {
|
|
|
214
280
|
}
|
|
215
281
|
function mapQuickOption(option, variantsInfo) {
|
|
216
282
|
if (!option) return null;
|
|
217
|
-
const optionId = option._id;
|
|
218
283
|
const choices = option.choicesSettings?.choices || [];
|
|
219
284
|
return {
|
|
220
|
-
_id:
|
|
285
|
+
_id: option._id || "",
|
|
221
286
|
name: option.name || "",
|
|
222
287
|
optionRenderType: mapOptionRenderType(option.optionRenderType),
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
isSelected: false
|
|
232
|
-
};
|
|
233
|
-
})
|
|
288
|
+
choices: choices.map((choice) => ({
|
|
289
|
+
choiceId: choice.choiceId || "",
|
|
290
|
+
name: choice.name || "",
|
|
291
|
+
choiceType: mapChoiceType(choice.choiceType),
|
|
292
|
+
colorCode: choice.colorCode || "",
|
|
293
|
+
inStock: choice.inStock ?? true,
|
|
294
|
+
isSelected: false
|
|
295
|
+
}))
|
|
234
296
|
};
|
|
235
297
|
}
|
|
236
|
-
|
|
237
|
-
|
|
298
|
+
function mapQuickAddOptions(product) {
|
|
299
|
+
const quickAddType = getQuickAddType(product);
|
|
300
|
+
if (quickAddType === QuickAddType.COLOR_AND_TEXT_OPTIONS) {
|
|
301
|
+
const colorOption = product.options.find((o) => o.optionRenderType === "SWATCH_CHOICES");
|
|
302
|
+
const textOption = product.options.find((o) => o.optionRenderType === "TEXT_CHOICES");
|
|
303
|
+
const quickOption = mapQuickOption(colorOption, product.variantsInfo);
|
|
304
|
+
const secondQuickOption = mapQuickOption(textOption, product.variantsInfo);
|
|
305
|
+
if (quickOption?.choices) {
|
|
306
|
+
const firstInStock = quickOption.choices.find((c) => c.inStock);
|
|
307
|
+
if (firstInStock) {
|
|
308
|
+
firstInStock.isSelected = true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (secondQuickOption?.choices) {
|
|
312
|
+
for (const choice of secondQuickOption.choices) {
|
|
313
|
+
choice.inStock = false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return { quickAddType, quickOption, secondQuickOption };
|
|
317
|
+
}
|
|
318
|
+
if (quickAddType === QuickAddType.SINGLE_OPTION) {
|
|
319
|
+
return {
|
|
320
|
+
quickAddType,
|
|
321
|
+
quickOption: mapQuickOption(product.options?.[0], product.variantsInfo),
|
|
322
|
+
secondQuickOption: null
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return { quickAddType, quickOption: null, secondQuickOption: null };
|
|
326
|
+
}
|
|
327
|
+
function buildVariantStockMap(product) {
|
|
328
|
+
const stockMap = {};
|
|
329
|
+
const options = product.options;
|
|
330
|
+
const variants = product.variantsInfo?.variants;
|
|
331
|
+
if (!options || options.length !== 2 || !variants) return stockMap;
|
|
332
|
+
const colorOption = options.find((o) => o.optionRenderType === "COLOR_SWATCH_CHOICES");
|
|
333
|
+
const textOption = options.find((o) => o.optionRenderType !== "COLOR_SWATCH_CHOICES");
|
|
334
|
+
if (!colorOption || !textOption) return stockMap;
|
|
335
|
+
const colorOptionId = colorOption._id || "";
|
|
336
|
+
const textOptionId = textOption._id || "";
|
|
337
|
+
const colorChoices = colorOption.choicesSettings?.choices || [];
|
|
338
|
+
const textChoices = textOption.choicesSettings?.choices || [];
|
|
339
|
+
for (const colorChoice of colorChoices) {
|
|
340
|
+
const cId = colorChoice.choiceId || "";
|
|
341
|
+
stockMap[cId] = {};
|
|
342
|
+
for (const textChoice of textChoices) {
|
|
343
|
+
const tId = textChoice.choiceId || "";
|
|
344
|
+
const variant = variants.find(
|
|
345
|
+
(v) => v.choices?.some(
|
|
346
|
+
(c) => c.optionChoiceIds?.optionId === colorOptionId && c.optionChoiceIds?.choiceId === cId
|
|
347
|
+
) && v.choices?.some(
|
|
348
|
+
(c) => c.optionChoiceIds?.optionId === textOptionId && c.optionChoiceIds?.choiceId === tId
|
|
349
|
+
)
|
|
350
|
+
);
|
|
351
|
+
stockMap[cId][tId] = variant?.inventoryStatus?.inStock ?? false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return stockMap;
|
|
355
|
+
}
|
|
356
|
+
function mapProductToCard(product, urls, tree) {
|
|
238
357
|
const mainMedia = product.media?.main;
|
|
239
358
|
const slug = product.slug || "";
|
|
240
|
-
const
|
|
241
|
-
const productUrl =
|
|
359
|
+
const mainCategoryId = product.mainCategoryId || "";
|
|
360
|
+
const productUrl = buildProductUrl(urls, tree, slug, mainCategoryId);
|
|
361
|
+
const categoryName = tree.slugMap.get(mainCategoryId);
|
|
242
362
|
const firstVariant = product.variantsInfo?.variants?.[0];
|
|
243
363
|
const variantPrice = firstVariant?.price;
|
|
244
364
|
const actualAmount = variantPrice?.actualPrice?.amount || product.actualPriceRange?.minValue?.amount || "0";
|
|
@@ -251,7 +371,7 @@ function mapProductToCard(product, productPagePath = DEFAULT_PRODUCT_PAGE_PATH,
|
|
|
251
371
|
name: product.name || "",
|
|
252
372
|
slug,
|
|
253
373
|
productUrl,
|
|
254
|
-
categoryPrefix:
|
|
374
|
+
categoryPrefix: categoryName,
|
|
255
375
|
mainMedia: {
|
|
256
376
|
url: mainMedia ? formatWixMediaUrl(mainMedia._id, mainMedia.url) : "",
|
|
257
377
|
altText: mainMedia?.altText || product.name || "",
|
|
@@ -263,7 +383,6 @@ function mapProductToCard(product, productPagePath = DEFAULT_PRODUCT_PAGE_PATH,
|
|
|
263
383
|
width: 300,
|
|
264
384
|
height: 300
|
|
265
385
|
},
|
|
266
|
-
// Simplified price fields
|
|
267
386
|
price: actualFormattedAmount,
|
|
268
387
|
strikethroughPrice: hasDiscount ? compareAtFormattedAmount : "",
|
|
269
388
|
hasDiscount,
|
|
@@ -282,11 +401,13 @@ function mapProductToCard(product, productPagePath = DEFAULT_PRODUCT_PAGE_PATH,
|
|
|
282
401
|
},
|
|
283
402
|
productType: mapProductType$1(product.productType),
|
|
284
403
|
isAddingToCart: false,
|
|
285
|
-
|
|
286
|
-
quickAddType: getQuickAddType(product),
|
|
287
|
-
quickOption: getQuickAddType(product) === QuickAddType.SINGLE_OPTION ? mapQuickOption(product.options?.[0], product.variantsInfo) : null
|
|
404
|
+
...mapQuickAddOptions(product)
|
|
288
405
|
};
|
|
289
406
|
}
|
|
407
|
+
function needsCategoryInfo(wixStores) {
|
|
408
|
+
const template = wixStores.urls.product;
|
|
409
|
+
return template.includes("{category}") || template.includes("{prefix}");
|
|
410
|
+
}
|
|
290
411
|
const PRICE_BUCKET_BOUNDARIES = [
|
|
291
412
|
0,
|
|
292
413
|
20,
|
|
@@ -397,18 +518,16 @@ const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX
|
|
|
397
518
|
const searchResult = await wixStores.products.searchProducts(
|
|
398
519
|
{
|
|
399
520
|
filter,
|
|
400
|
-
// @ts-expect-error - Wix SDK types don't match actual API
|
|
401
521
|
sort: sort.length > 0 ? sort : void 0,
|
|
402
522
|
cursorPaging,
|
|
403
523
|
search,
|
|
404
|
-
// @ts-expect-error - Wix SDK types don't include aggregations
|
|
405
524
|
aggregations
|
|
406
525
|
},
|
|
407
526
|
{
|
|
408
527
|
fields: [
|
|
409
528
|
"CURRENCY",
|
|
410
529
|
"VARIANT_OPTION_CHOICE_NAMES",
|
|
411
|
-
...wixStores
|
|
530
|
+
...needsCategoryInfo(wixStores) ? ["ALL_CATEGORIES_INFO"] : []
|
|
412
531
|
]
|
|
413
532
|
}
|
|
414
533
|
);
|
|
@@ -448,9 +567,9 @@ const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX
|
|
|
448
567
|
};
|
|
449
568
|
});
|
|
450
569
|
priceRanges.push(...bucketRanges);
|
|
451
|
-
const
|
|
570
|
+
const tree = await wixStores.getCategoryTree();
|
|
452
571
|
const mappedProducts = products.map(
|
|
453
|
-
(p) => mapProductToCard(p,
|
|
572
|
+
(p) => mapProductToCard(p, wixStores.urls, tree)
|
|
454
573
|
);
|
|
455
574
|
return {
|
|
456
575
|
products: mappedProducts,
|
|
@@ -476,11 +595,10 @@ const getProductBySlug = makeJayQuery("wixStores.getProductBySlug").withServices
|
|
|
476
595
|
throw new ActionError("INVALID_INPUT", "Product slug is required");
|
|
477
596
|
}
|
|
478
597
|
try {
|
|
479
|
-
const prefixConfig = wixStores.categoryPrefixes;
|
|
480
598
|
const fields = [
|
|
481
599
|
"MEDIA_ITEMS_INFO",
|
|
482
600
|
"VARIANT_OPTION_CHOICE_NAMES",
|
|
483
|
-
...
|
|
601
|
+
...needsCategoryInfo(wixStores) ? ["ALL_CATEGORIES_INFO"] : []
|
|
484
602
|
];
|
|
485
603
|
const result = await wixStores.products.getProductBySlug(slug, {
|
|
486
604
|
fields: [...fields]
|
|
@@ -489,13 +607,27 @@ const getProductBySlug = makeJayQuery("wixStores.getProductBySlug").withServices
|
|
|
489
607
|
if (!product) {
|
|
490
608
|
return null;
|
|
491
609
|
}
|
|
492
|
-
|
|
610
|
+
const tree = await wixStores.getCategoryTree();
|
|
611
|
+
return mapProductToCard(product, wixStores.urls, tree);
|
|
493
612
|
} catch (error) {
|
|
494
613
|
console.error("[wixStores.getProductBySlug] Failed to get product:", error);
|
|
495
614
|
return null;
|
|
496
615
|
}
|
|
497
616
|
}
|
|
498
617
|
);
|
|
618
|
+
const getVariantStock = makeJayQuery("wixStores.getVariantStock").withServices(WIX_STORES_SERVICE_MARKER).withHandler(
|
|
619
|
+
async (input, wixStores) => {
|
|
620
|
+
try {
|
|
621
|
+
const product = await wixStores.products.getProduct(input.productId, {
|
|
622
|
+
fields: ["VARIANT_OPTION_CHOICE_NAMES"]
|
|
623
|
+
});
|
|
624
|
+
return buildVariantStockMap(product);
|
|
625
|
+
} catch (error) {
|
|
626
|
+
console.error("[wixStores.getVariantStock] Failed:", error);
|
|
627
|
+
return {};
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
);
|
|
499
631
|
const getCategories = makeJayQuery("wixStores.getCategories").withServices(WIX_STORES_SERVICE_MARKER).withCaching({ maxAge: 3600 }).withHandler(
|
|
500
632
|
async (_input, wixStores) => {
|
|
501
633
|
try {
|
|
@@ -515,11 +647,153 @@ const getCategories = makeJayQuery("wixStores.getCategories").withServices(WIX_S
|
|
|
515
647
|
}
|
|
516
648
|
);
|
|
517
649
|
const PAGE_SIZE = 12;
|
|
650
|
+
function mapSortToAction(sort) {
|
|
651
|
+
switch (sort) {
|
|
652
|
+
case CurrentSort.priceAsc:
|
|
653
|
+
return "price_asc";
|
|
654
|
+
case CurrentSort.priceDesc:
|
|
655
|
+
return "price_desc";
|
|
656
|
+
case CurrentSort.newest:
|
|
657
|
+
return "newest";
|
|
658
|
+
case CurrentSort.nameAsc:
|
|
659
|
+
return "name_asc";
|
|
660
|
+
case CurrentSort.nameDesc:
|
|
661
|
+
return "name_desc";
|
|
662
|
+
default:
|
|
663
|
+
return "relevance";
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
function parseUrlFilters(url) {
|
|
667
|
+
try {
|
|
668
|
+
const params = new URL(url, "http://x").searchParams;
|
|
669
|
+
return {
|
|
670
|
+
searchTerm: params.get("q") || "",
|
|
671
|
+
selectedCategorySlugs: params.get("cat")?.split(",").filter(Boolean) || [],
|
|
672
|
+
minPrice: params.has("min") ? Number(params.get("min")) : null,
|
|
673
|
+
maxPrice: params.has("max") ? Number(params.get("max")) : null,
|
|
674
|
+
inStockOnly: params.get("inStock") === "1",
|
|
675
|
+
sort: params.get("sort") || "relevance"
|
|
676
|
+
};
|
|
677
|
+
} catch {
|
|
678
|
+
return {
|
|
679
|
+
searchTerm: "",
|
|
680
|
+
selectedCategorySlugs: [],
|
|
681
|
+
minPrice: null,
|
|
682
|
+
maxPrice: null,
|
|
683
|
+
inStockOnly: false,
|
|
684
|
+
sort: "relevance"
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function parseSortParam(sort) {
|
|
689
|
+
const sortMap = {
|
|
690
|
+
relevance: CurrentSort.relevance,
|
|
691
|
+
priceAsc: CurrentSort.priceAsc,
|
|
692
|
+
priceDesc: CurrentSort.priceDesc,
|
|
693
|
+
newest: CurrentSort.newest,
|
|
694
|
+
nameAsc: CurrentSort.nameAsc,
|
|
695
|
+
nameDesc: CurrentSort.nameDesc
|
|
696
|
+
};
|
|
697
|
+
return sortMap[sort] ?? CurrentSort.relevance;
|
|
698
|
+
}
|
|
699
|
+
const EMPTY_CATEGORY_HEADER = {
|
|
700
|
+
name: "",
|
|
701
|
+
description: "",
|
|
702
|
+
imageUrl: "",
|
|
703
|
+
hasImage: false,
|
|
704
|
+
productCount: 0,
|
|
705
|
+
breadcrumbs: [],
|
|
706
|
+
seoData: { tags: [], settings: { preventAutoRedirect: false, keywords: [] } }
|
|
707
|
+
};
|
|
708
|
+
async function findCategoryBySlug(categoriesClient, slug) {
|
|
709
|
+
const result = await categoriesClient.queryCategories({ treeReference: { appNamespace: "@wix/stores" } }).eq("slug", slug).eq("visible", true).limit(1).find();
|
|
710
|
+
return result.items?.[0] ?? null;
|
|
711
|
+
}
|
|
712
|
+
async function loadCategoryDetails(categoriesClient, categoryId) {
|
|
713
|
+
try {
|
|
714
|
+
return await categoriesClient.getCategory(categoryId, { appNamespace: "@wix/stores" }, { fields: ["DESCRIPTION", "BREADCRUMBS_INFO"] });
|
|
715
|
+
} catch {
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
async function buildCategoryHeader(wixStoreService, category, categoryUrlTemplate) {
|
|
720
|
+
const details = await loadCategoryDetails(wixStoreService.categories, category._id);
|
|
721
|
+
const cat = details || category;
|
|
722
|
+
const imageUrl = cat.image || "";
|
|
723
|
+
const description = cat.description || "";
|
|
724
|
+
const categoryTree = await wixStoreService.getCategoryTree();
|
|
725
|
+
const breadcrumbs = (cat.breadcrumbsInfo?.breadcrumbs || []).map((b) => ({
|
|
726
|
+
categoryId: b.categoryId,
|
|
727
|
+
name: b.categoryName,
|
|
728
|
+
slug: b.categorySlug,
|
|
729
|
+
url: categoryUrlTemplate ? buildCategoryUrl(wixStoreService.urls, categoryTree, b.categorySlug, b.categoryId) : ""
|
|
730
|
+
}));
|
|
731
|
+
const seoData = cat.seoData ? {
|
|
732
|
+
tags: (cat.seoData.tags || []).map((tag, index) => ({
|
|
733
|
+
position: index.toString().padStart(2, "0"),
|
|
734
|
+
type: tag.type || "",
|
|
735
|
+
props: Object.entries(tag.props || {}).map(([key, value]) => ({
|
|
736
|
+
key,
|
|
737
|
+
value
|
|
738
|
+
})),
|
|
739
|
+
meta: Object.entries(tag.meta || {}).map(([key, value]) => ({
|
|
740
|
+
key,
|
|
741
|
+
value
|
|
742
|
+
})),
|
|
743
|
+
children: tag.children || ""
|
|
744
|
+
})),
|
|
745
|
+
settings: {
|
|
746
|
+
preventAutoRedirect: cat.seoData.settings?.preventAutoRedirect || false,
|
|
747
|
+
keywords: (cat.seoData.settings?.keywords || []).map((k) => ({
|
|
748
|
+
term: k.term || "",
|
|
749
|
+
isMain: k.isMain || false,
|
|
750
|
+
origin: k.origin || ""
|
|
751
|
+
}))
|
|
752
|
+
}
|
|
753
|
+
} : EMPTY_CATEGORY_HEADER.seoData;
|
|
754
|
+
let header = {
|
|
755
|
+
name: cat.name || "",
|
|
756
|
+
description,
|
|
757
|
+
imageUrl,
|
|
758
|
+
hasImage: !!imageUrl,
|
|
759
|
+
productCount: cat.itemCounter || 0,
|
|
760
|
+
breadcrumbs,
|
|
761
|
+
seoData
|
|
762
|
+
};
|
|
763
|
+
if ((!description || !imageUrl) && cat.parentCategory?._id) {
|
|
764
|
+
const parent = await loadCategoryDetails(wixStoreService.categories, cat.parentCategory._id);
|
|
765
|
+
if (parent) {
|
|
766
|
+
if (!header.description && parent.description) {
|
|
767
|
+
header = { ...header, description: parent.description };
|
|
768
|
+
}
|
|
769
|
+
if (!header.imageUrl) {
|
|
770
|
+
const parentImage = parent.image || "";
|
|
771
|
+
if (parentImage) {
|
|
772
|
+
header = { ...header, imageUrl: parentImage, hasImage: true };
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return header;
|
|
778
|
+
}
|
|
518
779
|
async function renderSlowlyChanging$2(props, wixStores) {
|
|
519
780
|
const Pipeline = RenderPipeline.for();
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
const
|
|
781
|
+
const subcategorySlug = props.subcategory ?? null;
|
|
782
|
+
const categorySlug = props.category ?? null;
|
|
783
|
+
const defaultCategorySlug = wixStores.defaultCategory;
|
|
784
|
+
let activeCategory = null;
|
|
785
|
+
let baseCategoryId = null;
|
|
786
|
+
if (subcategorySlug) {
|
|
787
|
+
activeCategory = await findCategoryBySlug(wixStores.categories, subcategorySlug);
|
|
788
|
+
baseCategoryId = activeCategory?._id ?? null;
|
|
789
|
+
} else if (categorySlug) {
|
|
790
|
+
activeCategory = await findCategoryBySlug(wixStores.categories, categorySlug);
|
|
791
|
+
baseCategoryId = activeCategory?._id ?? null;
|
|
792
|
+
} else if (defaultCategorySlug) {
|
|
793
|
+
activeCategory = await findCategoryBySlug(wixStores.categories, defaultCategorySlug);
|
|
794
|
+
}
|
|
795
|
+
const tree = await wixStores.getCategoryTree();
|
|
796
|
+
const categoryHeader = activeCategory ? await buildCategoryHeader(wixStores, activeCategory, wixStores.urls.category) : EMPTY_CATEGORY_HEADER;
|
|
523
797
|
return Pipeline.try(async () => {
|
|
524
798
|
let query = wixStores.categories.queryCategories({
|
|
525
799
|
treeReference: { appNamespace: "@wix/stores" }
|
|
@@ -536,7 +810,8 @@ async function renderSlowlyChanging$2(props, wixStores) {
|
|
|
536
810
|
const categoryInfos = categories2.map((cat) => ({
|
|
537
811
|
categoryId: cat._id || "",
|
|
538
812
|
categoryName: cat.name || "",
|
|
539
|
-
categorySlug: cat.slug || ""
|
|
813
|
+
categorySlug: cat.slug || "",
|
|
814
|
+
categoryUrl: buildCategoryUrl(wixStores.urls, tree, cat.slug || "", cat._id || "") ?? ""
|
|
540
815
|
}));
|
|
541
816
|
return {
|
|
542
817
|
viewState: {
|
|
@@ -547,7 +822,8 @@ async function renderSlowlyChanging$2(props, wixStores) {
|
|
|
547
822
|
categoryFilter: {
|
|
548
823
|
categories: categoryInfos
|
|
549
824
|
}
|
|
550
|
-
}
|
|
825
|
+
},
|
|
826
|
+
categoryHeader
|
|
551
827
|
},
|
|
552
828
|
carryForward: {
|
|
553
829
|
searchFields: "name,description,sku",
|
|
@@ -560,11 +836,20 @@ async function renderSlowlyChanging$2(props, wixStores) {
|
|
|
560
836
|
}
|
|
561
837
|
async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
|
|
562
838
|
const Pipeline = RenderPipeline.for();
|
|
839
|
+
const urlFilters = parseUrlFilters(props.url);
|
|
840
|
+
const initialSort = parseSortParam(urlFilters.sort);
|
|
841
|
+
const initialCategoryIds = urlFilters.selectedCategorySlugs.map((slug) => slowCarryForward.categories.find((c) => c.categorySlug === slug)?.categoryId).filter(Boolean);
|
|
563
842
|
return Pipeline.try(async () => {
|
|
564
|
-
const baseCategoryIds = slowCarryForward.baseCategoryId ? [slowCarryForward.baseCategoryId] :
|
|
843
|
+
const baseCategoryIds = slowCarryForward.baseCategoryId ? [slowCarryForward.baseCategoryId, ...initialCategoryIds] : initialCategoryIds;
|
|
565
844
|
const result = await searchProducts({
|
|
566
|
-
query: "",
|
|
567
|
-
filters:
|
|
845
|
+
query: urlFilters.searchTerm || "",
|
|
846
|
+
filters: {
|
|
847
|
+
categoryIds: baseCategoryIds.length > 0 ? baseCategoryIds : void 0,
|
|
848
|
+
minPrice: urlFilters.minPrice ?? void 0,
|
|
849
|
+
maxPrice: urlFilters.maxPrice ?? void 0,
|
|
850
|
+
inStockOnly: urlFilters.inStockOnly || void 0
|
|
851
|
+
},
|
|
852
|
+
sortBy: initialSort !== CurrentSort.relevance ? mapSortToAction(initialSort) : void 0,
|
|
568
853
|
pageSize: PAGE_SIZE
|
|
569
854
|
});
|
|
570
855
|
return result;
|
|
@@ -607,20 +892,19 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
|
|
|
607
892
|
};
|
|
608
893
|
return {
|
|
609
894
|
viewState: {
|
|
610
|
-
searchExpression:
|
|
895
|
+
searchExpression: urlFilters.searchTerm,
|
|
611
896
|
isSearching: false,
|
|
612
|
-
hasSearched:
|
|
897
|
+
hasSearched: !!urlFilters.searchTerm,
|
|
613
898
|
searchResults: result.products,
|
|
614
899
|
resultCount: result.products.length,
|
|
615
900
|
hasResults: result.products.length > 0,
|
|
616
901
|
hasSuggestions: false,
|
|
617
902
|
suggestions: [],
|
|
618
903
|
filters: {
|
|
619
|
-
inStockOnly:
|
|
904
|
+
inStockOnly: urlFilters.inStockOnly,
|
|
620
905
|
priceRange: {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
maxPrice: priceAgg.maxBound,
|
|
906
|
+
minPrice: urlFilters.minPrice ?? priceAgg.minBound,
|
|
907
|
+
maxPrice: urlFilters.maxPrice ?? priceAgg.maxBound,
|
|
624
908
|
minBound: priceAgg.minBound,
|
|
625
909
|
maxBound: priceAgg.maxBound,
|
|
626
910
|
ranges: priceAgg.ranges
|
|
@@ -628,12 +912,12 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
|
|
|
628
912
|
categoryFilter: {
|
|
629
913
|
categories: slowCarryForward.categories.map((cat) => ({
|
|
630
914
|
categoryId: cat.categoryId,
|
|
631
|
-
isSelected:
|
|
915
|
+
isSelected: initialCategoryIds.includes(cat.categoryId)
|
|
632
916
|
}))
|
|
633
917
|
}
|
|
634
918
|
},
|
|
635
919
|
sortBy: {
|
|
636
|
-
currentSort:
|
|
920
|
+
currentSort: initialSort
|
|
637
921
|
},
|
|
638
922
|
hasMore: result.hasMore,
|
|
639
923
|
loadedCount: result.products.length,
|
|
@@ -648,7 +932,29 @@ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
|
|
|
648
932
|
};
|
|
649
933
|
});
|
|
650
934
|
}
|
|
651
|
-
|
|
935
|
+
async function* loadSearchParams([wixStores]) {
|
|
936
|
+
try {
|
|
937
|
+
const result = await wixStores.categories.queryCategories({
|
|
938
|
+
treeReference: { appNamespace: "@wix/stores" }
|
|
939
|
+
}).eq("visible", true).limit(100).find();
|
|
940
|
+
const categories2 = result.items || [];
|
|
941
|
+
yield categories2.filter((cat) => cat.slug && (cat.itemCounter ?? 0) > 0).map((cat) => ({
|
|
942
|
+
category: cat.slug
|
|
943
|
+
}));
|
|
944
|
+
for (const cat of categories2) {
|
|
945
|
+
if (!cat.slug || !cat.parentCategory?._id || (cat.itemCounter ?? 0) === 0)
|
|
946
|
+
continue;
|
|
947
|
+
const parent = categories2.find((c) => c._id === cat.parentCategory?._id);
|
|
948
|
+
if (parent?.slug) {
|
|
949
|
+
yield [{ category: parent.slug, subcategory: cat.slug }];
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
} catch (error) {
|
|
953
|
+
console.error("Failed to load category params:", error);
|
|
954
|
+
yield [];
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
const productSearch = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withLoadParams(loadSearchParams).withSlowlyRender(renderSlowlyChanging$2).withFastRender(renderFastChanging$1);
|
|
652
958
|
var ProductType = /* @__PURE__ */ ((ProductType2) => {
|
|
653
959
|
ProductType2[ProductType2["PHYSICAL"] = 0] = "PHYSICAL";
|
|
654
960
|
ProductType2[ProductType2["DIGITAL"] = 1] = "DIGITAL";
|
|
@@ -691,28 +997,21 @@ var MediaType = /* @__PURE__ */ ((MediaType2) => {
|
|
|
691
997
|
return MediaType2;
|
|
692
998
|
})(MediaType || {});
|
|
693
999
|
async function* loadProductParams([wixStores]) {
|
|
694
|
-
const
|
|
695
|
-
const
|
|
696
|
-
const fields =
|
|
1000
|
+
const template = wixStores.urls.product;
|
|
1001
|
+
const needsCategories = template.includes("{category}") || template.includes("{prefix}");
|
|
1002
|
+
const fields = needsCategories ? ["ALL_CATEGORIES_INFO"] : [];
|
|
697
1003
|
try {
|
|
698
1004
|
let result = await wixStores.products.queryProducts({ fields: [...fields] }).find();
|
|
699
|
-
yield result.items.map((product) =>
|
|
1005
|
+
yield result.items.map((product) => ({ slug: product.slug ?? "" })).filter((p) => p.slug);
|
|
700
1006
|
while (result.hasNext()) {
|
|
701
1007
|
result = await result.next();
|
|
702
|
-
yield result.items.map((product) =>
|
|
1008
|
+
yield result.items.map((product) => ({ slug: product.slug ?? "" })).filter((p) => p.slug);
|
|
703
1009
|
}
|
|
704
1010
|
} catch (error) {
|
|
705
1011
|
console.error("Failed to load product slugs:", error);
|
|
706
1012
|
yield [];
|
|
707
1013
|
}
|
|
708
1014
|
}
|
|
709
|
-
function mapProductToParams(product, prefixConfig) {
|
|
710
|
-
const prefix = resolveProductPrefix(product, prefixConfig);
|
|
711
|
-
return {
|
|
712
|
-
slug: product.slug ?? "",
|
|
713
|
-
...prefix ? { category: prefix } : {}
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
1015
|
function mapProductType(productType) {
|
|
717
1016
|
return productType === "DIGITAL" ? ProductType.DIGITAL : ProductType.PHYSICAL;
|
|
718
1017
|
}
|
|
@@ -848,8 +1147,8 @@ function mapVariants(variantsInfo) {
|
|
|
848
1147
|
}
|
|
849
1148
|
async function renderSlowlyChanging$1(props, wixStores) {
|
|
850
1149
|
const Pipeline = RenderPipeline.for();
|
|
851
|
-
const
|
|
852
|
-
const
|
|
1150
|
+
const template = wixStores.urls.product;
|
|
1151
|
+
const needsCategories = template.includes("{category}") || template.includes("{prefix}");
|
|
853
1152
|
return Pipeline.try(async () => {
|
|
854
1153
|
const fields = [
|
|
855
1154
|
"INFO_SECTION",
|
|
@@ -857,17 +1156,11 @@ async function renderSlowlyChanging$1(props, wixStores) {
|
|
|
857
1156
|
"MEDIA_ITEMS_INFO",
|
|
858
1157
|
"PLAIN_DESCRIPTION",
|
|
859
1158
|
"CURRENCY",
|
|
860
|
-
...
|
|
1159
|
+
...needsCategories ? ["ALL_CATEGORIES_INFO"] : []
|
|
861
1160
|
];
|
|
862
1161
|
const response = await wixStores.products.getProductBySlug(props.slug, {
|
|
863
1162
|
fields: [...fields]
|
|
864
1163
|
});
|
|
865
|
-
if (props.category && hasPrefixes) {
|
|
866
|
-
const actualPrefix = resolveProductPrefix(response.product, prefixConfig);
|
|
867
|
-
if (actualPrefix !== props.category) {
|
|
868
|
-
throw new Error("Category prefix mismatch");
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
1164
|
return response;
|
|
872
1165
|
}).recover((error) => {
|
|
873
1166
|
console.log("product page error", error);
|
|
@@ -960,37 +1253,38 @@ const categoryList = makeJayStackComponent().withProps().withServices(WIX_STORES
|
|
|
960
1253
|
const WIX_STORES_CONTEXT = createJayContext();
|
|
961
1254
|
function loadWixStoresConfig() {
|
|
962
1255
|
const configPath = path.join(process.cwd(), "config", ".wix-stores.yaml");
|
|
1256
|
+
const defaults = {
|
|
1257
|
+
urls: { product: "/products/{slug}", category: null },
|
|
1258
|
+
defaultCategory: null
|
|
1259
|
+
};
|
|
963
1260
|
if (!fs.existsSync(configPath)) {
|
|
964
|
-
return
|
|
1261
|
+
return defaults;
|
|
965
1262
|
}
|
|
966
1263
|
const fileContents = fs.readFileSync(configPath, "utf8");
|
|
967
1264
|
const raw = yaml.load(fileContents);
|
|
968
1265
|
if (!raw) {
|
|
969
|
-
return
|
|
1266
|
+
return defaults;
|
|
970
1267
|
}
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
});
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
return { categoryPrefixes: prefixes };
|
|
1268
|
+
const urls = raw.urls;
|
|
1269
|
+
return {
|
|
1270
|
+
urls: {
|
|
1271
|
+
product: typeof urls?.product === "string" ? urls.product : defaults.urls.product,
|
|
1272
|
+
category: typeof urls?.category === "string" ? urls.category : null
|
|
1273
|
+
},
|
|
1274
|
+
defaultCategory: typeof raw.defaultCategory === "string" ? raw.defaultCategory : null
|
|
1275
|
+
};
|
|
984
1276
|
}
|
|
985
1277
|
const init = makeJayInit().withServer(async () => {
|
|
986
1278
|
console.log("[wix-stores] Initializing Wix Stores service...");
|
|
987
1279
|
const wixClient = getService(WIX_CLIENT_SERVICE);
|
|
988
1280
|
const storesConfig = loadWixStoresConfig();
|
|
989
1281
|
provideWixStoresService(wixClient, {
|
|
990
|
-
|
|
1282
|
+
urls: storesConfig.urls,
|
|
1283
|
+
defaultCategory: storesConfig.defaultCategory
|
|
991
1284
|
});
|
|
992
|
-
|
|
993
|
-
|
|
1285
|
+
console.log(`[wix-stores] URL templates: product="${storesConfig.urls.product}", category="${storesConfig.urls.category ?? "none"}"`);
|
|
1286
|
+
if (storesConfig.defaultCategory) {
|
|
1287
|
+
console.log(`[wix-stores] Default category: ${storesConfig.defaultCategory}`);
|
|
994
1288
|
}
|
|
995
1289
|
console.log("[wix-stores] Server initialization complete");
|
|
996
1290
|
return {
|
|
@@ -1001,20 +1295,22 @@ const init = makeJayInit().withServer(async () => {
|
|
|
1001
1295
|
const CONFIG_FILE_NAME = ".wix-stores.yaml";
|
|
1002
1296
|
const CONFIG_TEMPLATE = `# Wix Stores Configuration
|
|
1003
1297
|
#
|
|
1004
|
-
#
|
|
1005
|
-
#
|
|
1006
|
-
#
|
|
1007
|
-
#
|
|
1298
|
+
# URL templates for link generation.
|
|
1299
|
+
# Placeholders: {slug} (product), {category} (sub-category), {prefix} (root category)
|
|
1300
|
+
#
|
|
1301
|
+
# urls:
|
|
1302
|
+
# product: "/products/{slug}" # simple (default)
|
|
1303
|
+
# product: "/products/{category}/{slug}" # with categories
|
|
1304
|
+
# product: "/products/{prefix}/{category}/{slug}" # with prefixes + categories
|
|
1305
|
+
# category: "/products/{prefix}/{category}" # category deep-link pages
|
|
1008
1306
|
#
|
|
1009
|
-
#
|
|
1307
|
+
# Fallback category for pages without category context:
|
|
1308
|
+
# defaultCategory: "all-products"
|
|
1010
1309
|
#
|
|
1011
|
-
#
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
# - categoryId: "<another-root-category-id>"
|
|
1016
|
-
# prefix: "<another-url-prefix>"
|
|
1017
|
-
# name: "<another-display-name>"
|
|
1310
|
+
# To see available categories: jay-stack setup wix-stores (generates category tree reference)
|
|
1311
|
+
|
|
1312
|
+
urls:
|
|
1313
|
+
product: "/products/{slug}"
|
|
1018
1314
|
`;
|
|
1019
1315
|
async function setupWixStores(ctx) {
|
|
1020
1316
|
if (ctx.initError) {
|
|
@@ -1041,8 +1337,7 @@ async function setupWixStores(ctx) {
|
|
|
1041
1337
|
configCreated.push(`config/${CONFIG_FILE_NAME}`);
|
|
1042
1338
|
}
|
|
1043
1339
|
const service = getService(WIX_STORES_SERVICE_MARKER);
|
|
1044
|
-
const
|
|
1045
|
-
const message = prefixCount > 0 ? `Wix Stores configured with ${prefixCount} category prefix(es): ${service.categoryPrefixes.map((p) => p.prefix).join(", ")}` : "Wix Stores service verified";
|
|
1340
|
+
const message = `Wix Stores configured (product URL: ${service.urls.product})`;
|
|
1046
1341
|
return {
|
|
1047
1342
|
status: "configured",
|
|
1048
1343
|
message,
|
|
@@ -1091,22 +1386,13 @@ async function generateWixStoresReferences(ctx) {
|
|
|
1091
1386
|
roots.push(node);
|
|
1092
1387
|
}
|
|
1093
1388
|
}
|
|
1094
|
-
const prefixConfig = storesService.categoryPrefixes;
|
|
1095
|
-
const configuredPrefixes = prefixConfig.map((p) => ({
|
|
1096
|
-
categoryId: p.categoryId,
|
|
1097
|
-
prefix: p.prefix,
|
|
1098
|
-
name: p.name,
|
|
1099
|
-
categoryName: nodeMap.get(p.categoryId)?.name ?? "unknown"
|
|
1100
|
-
}));
|
|
1101
1389
|
const categoriesPath = path.join(ctx.referencesDir, "categories.yaml");
|
|
1102
1390
|
fs.writeFileSync(
|
|
1103
1391
|
categoriesPath,
|
|
1104
1392
|
yaml.dump(
|
|
1105
1393
|
{
|
|
1106
|
-
|
|
1107
|
-
_description: "Wix Stores category tree for agent discovery. Shows category hierarchy, IDs, product counts, and configured URL prefixes.",
|
|
1394
|
+
_description: "Wix Stores category tree for agent discovery. Shows category hierarchy, IDs, slugs, product counts, and parent-child relationships.",
|
|
1108
1395
|
totalCategories: allCategories.length,
|
|
1109
|
-
configuredPrefixes: configuredPrefixes.length > 0 ? configuredPrefixes : void 0,
|
|
1110
1396
|
categoryTree: roots
|
|
1111
1397
|
},
|
|
1112
1398
|
{ indent: 2, lineWidth: 120, noRefs: true }
|
|
@@ -1123,12 +1409,18 @@ export {
|
|
|
1123
1409
|
WIX_CART_SERVICE,
|
|
1124
1410
|
WIX_STORES_CONTEXT,
|
|
1125
1411
|
WIX_STORES_SERVICE_MARKER,
|
|
1412
|
+
buildCategoryUrl,
|
|
1413
|
+
buildProductUrl,
|
|
1126
1414
|
cartIndicator,
|
|
1127
1415
|
cartPage,
|
|
1128
1416
|
categoryList,
|
|
1417
|
+
findCategoryImage,
|
|
1418
|
+
findRootCategoryId,
|
|
1419
|
+
findRootCategorySlug,
|
|
1129
1420
|
generateWixStoresReferences,
|
|
1130
1421
|
getCategories,
|
|
1131
1422
|
getProductBySlug,
|
|
1423
|
+
getVariantStock,
|
|
1132
1424
|
init,
|
|
1133
1425
|
productPage,
|
|
1134
1426
|
productSearch,
|