@jay-framework/wix-stores 0.15.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/index.js ADDED
@@ -0,0 +1,1140 @@
1
+ import { getCurrentCartClient } from "@jay-framework/wix-cart";
2
+ import { WIX_CART_CONTEXT, WIX_CART_SERVICE, cartIndicator, cartPage, provideWixCartContext, provideWixCartService } from "@jay-framework/wix-cart";
3
+ import { createJayService, makeJayQuery, ActionError, makeJayStackComponent, RenderPipeline, makeJayInit } from "@jay-framework/fullstack-component";
4
+ import { inventoryItemsV3, productsV3 } from "@wix/stores";
5
+ import { categories } from "@wix/categories";
6
+ import { registerService, getService } from "@jay-framework/stack-server-runtime";
7
+ import { createJayContext } from "@jay-framework/runtime";
8
+ import "@jay-framework/component";
9
+ import { WIX_CLIENT_SERVICE } from "@jay-framework/wix-server-client";
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ import * as yaml from "js-yaml";
13
+ var CurrentSort = /* @__PURE__ */ ((CurrentSort2) => {
14
+ CurrentSort2[CurrentSort2["relevance"] = 0] = "relevance";
15
+ CurrentSort2[CurrentSort2["priceAsc"] = 1] = "priceAsc";
16
+ CurrentSort2[CurrentSort2["priceDesc"] = 2] = "priceDesc";
17
+ CurrentSort2[CurrentSort2["newest"] = 3] = "newest";
18
+ CurrentSort2[CurrentSort2["nameAsc"] = 4] = "nameAsc";
19
+ CurrentSort2[CurrentSort2["nameDesc"] = 5] = "nameDesc";
20
+ return CurrentSort2;
21
+ })(CurrentSort || {});
22
+ const instances = {
23
+ productsV3ClientInstance: void 0,
24
+ categoriesClientInstance: void 0,
25
+ inventoryV3ClientInstance: void 0
26
+ };
27
+ function getProductsV3Client(wixClient) {
28
+ if (!instances.productsV3ClientInstance) {
29
+ instances.productsV3ClientInstance = wixClient.use(productsV3);
30
+ }
31
+ return instances.productsV3ClientInstance;
32
+ }
33
+ function getCategoriesClient(wixClient) {
34
+ if (!instances.categoriesClientInstance) {
35
+ instances.categoriesClientInstance = wixClient.use(categories);
36
+ }
37
+ return instances.categoriesClientInstance;
38
+ }
39
+ function getInventoryClient(wixClient) {
40
+ if (!instances.inventoryV3ClientInstance) {
41
+ instances.inventoryV3ClientInstance = wixClient.use(inventoryItemsV3);
42
+ }
43
+ return instances.inventoryV3ClientInstance;
44
+ }
45
+ const WIX_STORES_SERVICE_MARKER = createJayService("Wix Store Service");
46
+ function provideWixStoresService(wixClient, options) {
47
+ const service = {
48
+ products: getProductsV3Client(wixClient),
49
+ categories: getCategoriesClient(wixClient),
50
+ inventory: getInventoryClient(wixClient),
51
+ // Keep cart for backward compatibility, but prefer WIX_CART_SERVICE
52
+ cart: getCurrentCartClient(wixClient),
53
+ categoryPrefixes: options?.categoryPrefixes ?? []
54
+ };
55
+ registerService(WIX_STORES_SERVICE_MARKER, service);
56
+ return service;
57
+ }
58
+ var MediaType$1 = /* @__PURE__ */ ((MediaType2) => {
59
+ MediaType2[MediaType2["IMAGE"] = 0] = "IMAGE";
60
+ MediaType2[MediaType2["VIDEO"] = 1] = "VIDEO";
61
+ return MediaType2;
62
+ })(MediaType$1 || {});
63
+ var AvailabilityStatus = /* @__PURE__ */ ((AvailabilityStatus2) => {
64
+ AvailabilityStatus2[AvailabilityStatus2["IN_STOCK"] = 0] = "IN_STOCK";
65
+ AvailabilityStatus2[AvailabilityStatus2["OUT_OF_STOCK"] = 1] = "OUT_OF_STOCK";
66
+ AvailabilityStatus2[AvailabilityStatus2["PARTIALLY_OUT_OF_STOCK"] = 2] = "PARTIALLY_OUT_OF_STOCK";
67
+ return AvailabilityStatus2;
68
+ })(AvailabilityStatus || {});
69
+ var PreorderStatus = /* @__PURE__ */ ((PreorderStatus2) => {
70
+ PreorderStatus2[PreorderStatus2["ENABLED"] = 0] = "ENABLED";
71
+ PreorderStatus2[PreorderStatus2["DISABLED"] = 1] = "DISABLED";
72
+ PreorderStatus2[PreorderStatus2["PARTIALLY_ENABLED"] = 2] = "PARTIALLY_ENABLED";
73
+ return PreorderStatus2;
74
+ })(PreorderStatus || {});
75
+ var ProductType$1 = /* @__PURE__ */ ((ProductType2) => {
76
+ ProductType2[ProductType2["PHYSICAL"] = 0] = "PHYSICAL";
77
+ ProductType2[ProductType2["DIGITAL"] = 1] = "DIGITAL";
78
+ return ProductType2;
79
+ })(ProductType$1 || {});
80
+ var QuickAddType = /* @__PURE__ */ ((QuickAddType2) => {
81
+ QuickAddType2[QuickAddType2["SIMPLE"] = 0] = "SIMPLE";
82
+ QuickAddType2[QuickAddType2["SINGLE_OPTION"] = 1] = "SINGLE_OPTION";
83
+ QuickAddType2[QuickAddType2["NEEDS_CONFIGURATION"] = 2] = "NEEDS_CONFIGURATION";
84
+ return QuickAddType2;
85
+ })(QuickAddType || {});
86
+ var OptionRenderType$1 = /* @__PURE__ */ ((OptionRenderType2) => {
87
+ OptionRenderType2[OptionRenderType2["TEXT_CHOICES"] = 0] = "TEXT_CHOICES";
88
+ OptionRenderType2[OptionRenderType2["COLOR_SWATCH_CHOICES"] = 1] = "COLOR_SWATCH_CHOICES";
89
+ return OptionRenderType2;
90
+ })(OptionRenderType$1 || {});
91
+ var ChoiceType$1 = /* @__PURE__ */ ((ChoiceType2) => {
92
+ ChoiceType2[ChoiceType2["CHOICE_TEXT"] = 0] = "CHOICE_TEXT";
93
+ ChoiceType2[ChoiceType2["ONE_COLOR"] = 1] = "ONE_COLOR";
94
+ return ChoiceType2;
95
+ })(ChoiceType$1 || {});
96
+ function parseWixMediaUrl(url) {
97
+ if (!url) return null;
98
+ const match = url.match(
99
+ /^wix:(image|video|document|audio):\/\/v1\/([^/]+)\/([^#]+)(?:#(.*))?$/
100
+ );
101
+ if (!match) return null;
102
+ const [, type, mediaId, fileName, hashParams] = match;
103
+ const result = {
104
+ type,
105
+ mediaId,
106
+ fileName: decodeURIComponent(fileName)
107
+ };
108
+ if (hashParams) {
109
+ const params = new URLSearchParams(hashParams);
110
+ const originWidth = params.get("originWidth");
111
+ const originHeight = params.get("originHeight");
112
+ if (originWidth) result.originWidth = parseInt(originWidth, 10);
113
+ if (originHeight) result.originHeight = parseInt(originHeight, 10);
114
+ const posterUri = params.get("posterUri");
115
+ const posterWidth = params.get("posterWidth");
116
+ const posterHeight = params.get("posterHeight");
117
+ if (posterUri) result.posterUri = decodeURIComponent(posterUri);
118
+ if (posterWidth) result.posterWidth = parseInt(posterWidth, 10);
119
+ if (posterHeight) result.posterHeight = parseInt(posterHeight, 10);
120
+ }
121
+ return result;
122
+ }
123
+ function formatWixMediaUrl(_id, url, resize) {
124
+ const resizeFragment = resize ? `/v1/fit/w_${resize.w},h_${resize.h},q_90/file.jpg` : "";
125
+ if (url == null ? void 0 : url.startsWith("wix:")) {
126
+ const parsed = parseWixMediaUrl(url);
127
+ if (parsed) {
128
+ return `https://static.wixstatic.com/media/${parsed.mediaId}${resizeFragment}`;
129
+ }
130
+ }
131
+ if ((url == null ? void 0 : url.startsWith("http://")) || (url == null ? void 0 : url.startsWith("https://"))) {
132
+ return url;
133
+ }
134
+ if (_id) {
135
+ return `https://static.wixstatic.com/media/${_id}${resizeFragment}`;
136
+ }
137
+ return "";
138
+ }
139
+ function resolveProductPrefix(product, prefixConfig) {
140
+ if (!prefixConfig?.length || !product.allCategoriesInfo?.categories) {
141
+ return null;
142
+ }
143
+ const productCategoryIds = new Set(
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;
149
+ }
150
+ }
151
+ return null;
152
+ }
153
+ function resolveProductPrefixConfig(product, prefixConfig) {
154
+ if (!prefixConfig?.length || !product.allCategoriesInfo?.categories) {
155
+ return null;
156
+ }
157
+ const productCategoryIds = new Set(
158
+ product.allCategoriesInfo.categories.map((c) => c._id).filter(Boolean)
159
+ );
160
+ for (const config of prefixConfig) {
161
+ if (productCategoryIds.has(config.categoryId)) {
162
+ return config;
163
+ }
164
+ }
165
+ return null;
166
+ }
167
+ function mapAvailabilityStatus(status) {
168
+ switch (status) {
169
+ case "OUT_OF_STOCK":
170
+ return AvailabilityStatus.OUT_OF_STOCK;
171
+ case "PARTIALLY_OUT_OF_STOCK":
172
+ return AvailabilityStatus.PARTIALLY_OUT_OF_STOCK;
173
+ default:
174
+ return AvailabilityStatus.IN_STOCK;
175
+ }
176
+ }
177
+ function mapPreorderStatus(status) {
178
+ switch (status) {
179
+ case "ENABLED":
180
+ return PreorderStatus.ENABLED;
181
+ case "PARTIALLY_ENABLED":
182
+ return PreorderStatus.PARTIALLY_ENABLED;
183
+ default:
184
+ return PreorderStatus.DISABLED;
185
+ }
186
+ }
187
+ function mapMediaType$1(mediaType) {
188
+ return mediaType === "VIDEO" ? MediaType$1.VIDEO : MediaType$1.IMAGE;
189
+ }
190
+ function mapProductType$1(productType) {
191
+ return productType === "DIGITAL" ? ProductType$1.DIGITAL : ProductType$1.PHYSICAL;
192
+ }
193
+ function isValidPrice(amount) {
194
+ if (!amount) return false;
195
+ const numAmount = parseFloat(amount);
196
+ return !isNaN(numAmount) && numAmount > 0;
197
+ }
198
+ function getQuickAddType(product) {
199
+ const optionCount = product.options?.length ?? 0;
200
+ const hasModifiers = (product.modifiers?.length ?? 0) > 0;
201
+ if (hasModifiers || optionCount > 1) {
202
+ return QuickAddType.NEEDS_CONFIGURATION;
203
+ }
204
+ if (optionCount === 1) {
205
+ return QuickAddType.SINGLE_OPTION;
206
+ }
207
+ return QuickAddType.SIMPLE;
208
+ }
209
+ function mapOptionRenderType(renderType) {
210
+ return renderType === "COLOR_SWATCH_CHOICES" ? OptionRenderType$1.COLOR_SWATCH_CHOICES : OptionRenderType$1.TEXT_CHOICES;
211
+ }
212
+ function mapChoiceType(choiceType) {
213
+ return choiceType === "ONE_COLOR" ? ChoiceType$1.ONE_COLOR : ChoiceType$1.CHOICE_TEXT;
214
+ }
215
+ function mapQuickOption(option, variantsInfo) {
216
+ if (!option) return null;
217
+ const optionId = option._id;
218
+ const choices = option.choicesSettings?.choices || [];
219
+ return {
220
+ _id: optionId,
221
+ name: option.name || "",
222
+ optionRenderType: mapOptionRenderType(option.optionRenderType),
223
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
224
+ choices: choices.map((choice) => {
225
+ return {
226
+ choiceId: choice.choiceId,
227
+ name: choice.name || "",
228
+ choiceType: mapChoiceType(choice.choiceType),
229
+ colorCode: choice.colorCode || "",
230
+ inStock: choice.inStock,
231
+ isSelected: false
232
+ };
233
+ })
234
+ };
235
+ }
236
+ const DEFAULT_PRODUCT_PAGE_PATH = "/products";
237
+ function mapProductToCard(product, productPagePath = DEFAULT_PRODUCT_PAGE_PATH, prefixConfig) {
238
+ const mainMedia = product.media?.main;
239
+ const slug = product.slug || "";
240
+ const matchedPrefix = prefixConfig?.length ? resolveProductPrefixConfig(product, prefixConfig) : null;
241
+ const productUrl = slug ? matchedPrefix ? `${productPagePath}/${matchedPrefix.prefix}/${slug}` : `${productPagePath}/${slug}` : "";
242
+ const firstVariant = product.variantsInfo?.variants?.[0];
243
+ const variantPrice = firstVariant?.price;
244
+ const actualAmount = variantPrice?.actualPrice?.amount || product.actualPriceRange?.minValue?.amount || "0";
245
+ const actualFormattedAmount = variantPrice?.actualPrice?.formattedAmount || product.actualPriceRange?.minValue?.formattedAmount || "";
246
+ const compareAtAmount = variantPrice?.compareAtPrice?.amount || product.compareAtPriceRange?.minValue?.amount;
247
+ const compareAtFormattedAmount = variantPrice?.compareAtPrice?.formattedAmount || product.compareAtPriceRange?.minValue?.formattedAmount || "";
248
+ const hasDiscount = isValidPrice(compareAtAmount) && compareAtAmount !== actualAmount;
249
+ return {
250
+ _id: product._id || "",
251
+ name: product.name || "",
252
+ slug,
253
+ productUrl,
254
+ categoryPrefix: matchedPrefix?.name ?? "",
255
+ mainMedia: {
256
+ url: mainMedia ? formatWixMediaUrl(mainMedia._id, mainMedia.url) : "",
257
+ altText: mainMedia?.altText || product.name || "",
258
+ mediaType: mapMediaType$1(mainMedia?.mediaType)
259
+ },
260
+ thumbnail: {
261
+ url: mainMedia ? formatWixMediaUrl(mainMedia._id, mainMedia.url, { w: 300, h: 300 }) : "",
262
+ altText: mainMedia?.altText || product.name || "",
263
+ width: 300,
264
+ height: 300
265
+ },
266
+ // Simplified price fields
267
+ price: actualFormattedAmount,
268
+ strikethroughPrice: hasDiscount ? compareAtFormattedAmount : "",
269
+ hasDiscount,
270
+ inventory: {
271
+ availabilityStatus: mapAvailabilityStatus(product.inventory?.availabilityStatus),
272
+ preorderStatus: mapPreorderStatus(product.inventory?.preorderStatus)
273
+ },
274
+ ribbon: {
275
+ _id: product.ribbon?._id || "",
276
+ name: product.ribbon?.name || ""
277
+ },
278
+ hasRibbon: !!product.ribbon?.name,
279
+ brand: {
280
+ _id: product.brand?._id || "",
281
+ name: product.brand?.name || ""
282
+ },
283
+ productType: mapProductType$1(product.productType),
284
+ isAddingToCart: false,
285
+ // Quick add behavior
286
+ quickAddType: getQuickAddType(product),
287
+ quickOption: getQuickAddType(product) === QuickAddType.SINGLE_OPTION ? mapQuickOption(product.options?.[0], product.variantsInfo) : null
288
+ };
289
+ }
290
+ const PRICE_BUCKET_BOUNDARIES = [
291
+ 0,
292
+ 20,
293
+ 40,
294
+ 100,
295
+ 200,
296
+ 400,
297
+ 1e3,
298
+ 2e3,
299
+ 4e3,
300
+ 1e4,
301
+ 2e4,
302
+ 4e4,
303
+ 1e5
304
+ ];
305
+ const PRICE_BUCKETS = PRICE_BUCKET_BOUNDARIES.slice(0, -1).map((from, i) => ({
306
+ from,
307
+ to: PRICE_BUCKET_BOUNDARIES[i + 1]
308
+ }));
309
+ PRICE_BUCKETS.push({ from: PRICE_BUCKET_BOUNDARIES[PRICE_BUCKET_BOUNDARIES.length - 1] });
310
+ const searchProducts = makeJayQuery("wixStores.searchProducts").withServices(WIX_STORES_SERVICE_MARKER).withHandler(
311
+ async (input, wixStores) => {
312
+ const { query, filters = {}, sortBy = "relevance", cursor, pageSize = 12 } = input;
313
+ try {
314
+ const filterConditions = [
315
+ // Only visible products
316
+ { visible: { $eq: true } }
317
+ ];
318
+ const hasMinPrice = filters.minPrice !== void 0 && filters.minPrice > 0;
319
+ const hasMaxPrice = filters.maxPrice !== void 0 && filters.maxPrice > 0;
320
+ if (hasMinPrice) {
321
+ filterConditions.push({
322
+ "actualPriceRange.minValue.amount": { $gte: String(filters.minPrice) }
323
+ });
324
+ }
325
+ if (hasMaxPrice) {
326
+ filterConditions.push({
327
+ "actualPriceRange.minValue.amount": { $lte: String(filters.maxPrice) }
328
+ });
329
+ }
330
+ if (filters.inStockOnly) {
331
+ filterConditions.push({
332
+ "inventory.availabilityStatus": { $eq: "IN_STOCK" }
333
+ });
334
+ }
335
+ if (filters.categoryIds && filters.categoryIds.length > 0) {
336
+ filterConditions.push({
337
+ "allCategoriesInfo.categories.id": {
338
+ $hasAll: filters.categoryIds
339
+ }
340
+ });
341
+ }
342
+ const filter = filterConditions.length === 1 ? filterConditions[0] : { $and: filterConditions };
343
+ const sort = [];
344
+ switch (sortBy) {
345
+ case "price_asc":
346
+ sort.push({ fieldName: "actualPriceRange.minValue.amount", order: "ASC" });
347
+ break;
348
+ case "price_desc":
349
+ sort.push({ fieldName: "actualPriceRange.minValue.amount", order: "DESC" });
350
+ break;
351
+ case "name_asc":
352
+ sort.push({ fieldName: "name", order: "ASC" });
353
+ break;
354
+ case "name_desc":
355
+ sort.push({ fieldName: "name", order: "DESC" });
356
+ break;
357
+ case "newest":
358
+ sort.push({ fieldName: "_createdDate", order: "DESC" });
359
+ break;
360
+ }
361
+ const cursorPaging = cursor ? { cursor, limit: pageSize } : { limit: pageSize };
362
+ const hasSearchQuery = query && query.trim().length > 0;
363
+ const search = hasSearchQuery ? {
364
+ expression: query.trim(),
365
+ fields: ["name", "description"]
366
+ } : void 0;
367
+ const aggregations = [
368
+ // Total count via COUNT_DISTINCT on slug
369
+ {
370
+ fieldPath: "slug",
371
+ name: "total-count",
372
+ type: "SCALAR",
373
+ scalar: { type: "COUNT_DISTINCT" }
374
+ },
375
+ // Price buckets with product counts
376
+ {
377
+ fieldPath: "actualPriceRange.minValue.amount",
378
+ name: "price-buckets",
379
+ type: "RANGE",
380
+ range: { buckets: PRICE_BUCKETS }
381
+ },
382
+ // Min price for slider bound
383
+ {
384
+ fieldPath: "actualPriceRange.minValue.amount",
385
+ name: "min-price",
386
+ type: "SCALAR",
387
+ scalar: { type: "MIN" }
388
+ },
389
+ // Max price for slider bound
390
+ {
391
+ fieldPath: "actualPriceRange.minValue.amount",
392
+ name: "max-price",
393
+ type: "SCALAR",
394
+ scalar: { type: "MAX" }
395
+ }
396
+ ];
397
+ const searchResult = await wixStores.products.searchProducts(
398
+ {
399
+ filter,
400
+ // @ts-expect-error - Wix SDK types don't match actual API
401
+ sort: sort.length > 0 ? sort : void 0,
402
+ cursorPaging,
403
+ search,
404
+ // @ts-expect-error - Wix SDK types don't include aggregations
405
+ aggregations
406
+ },
407
+ {
408
+ fields: [
409
+ "CURRENCY",
410
+ "VARIANT_OPTION_CHOICE_NAMES",
411
+ ...wixStores.categoryPrefixes.length > 0 ? ["ALL_CATEGORIES_INFO"] : []
412
+ ]
413
+ }
414
+ );
415
+ const products = searchResult.products || [];
416
+ const nextCursor = searchResult.pagingMetadata?.cursors?.next || null;
417
+ const aggResults = searchResult.aggregationData?.results || [];
418
+ const totalCountAgg = aggResults.find((a) => a.name === "total-count")?.scalar;
419
+ const minPriceAgg = aggResults.find((a) => a.name === "min-price")?.scalar;
420
+ const maxPriceAgg = aggResults.find((a) => a.name === "max-price")?.scalar;
421
+ const totalCount = totalCountAgg?.value ?? products.length;
422
+ const bucketsAgg = aggResults.find((a) => a.name === "price-buckets").ranges;
423
+ const minBound = minPriceAgg?.value;
424
+ const maxBound = maxPriceAgg?.value;
425
+ const buckets = bucketsAgg.results || [];
426
+ const currencySymbol = products[0]?.currency === "ILS" ? "₪" : products[0]?.currency === "USD" ? "$" : products[0]?.currency === "EUR" ? "€" : products[0]?.currency === "GBP" ? "£" : "$";
427
+ const priceRanges = [
428
+ {
429
+ rangeId: "all",
430
+ label: "Show all",
431
+ minValue: null,
432
+ maxValue: null,
433
+ productCount: totalCount,
434
+ isSelected: true
435
+ }
436
+ ];
437
+ const bucketRanges = buckets.filter((bucket) => (bucket.count ?? 0) > 0).map((bucket) => {
438
+ const from = bucket.from ?? 0;
439
+ const to = bucket.to;
440
+ const label = to ? `${currencySymbol}${from} - ${currencySymbol}${to}` : `${currencySymbol}${from}+`;
441
+ return {
442
+ rangeId: `${from}-${to ?? "plus"}`,
443
+ label,
444
+ minValue: from,
445
+ maxValue: to ?? null,
446
+ productCount: bucket.count ?? 0,
447
+ isSelected: false
448
+ };
449
+ });
450
+ priceRanges.push(...bucketRanges);
451
+ const prefixConfig = wixStores.categoryPrefixes;
452
+ const mappedProducts = products.map(
453
+ (p) => mapProductToCard(p, "/products", prefixConfig)
454
+ );
455
+ return {
456
+ products: mappedProducts,
457
+ totalCount,
458
+ nextCursor,
459
+ hasMore: nextCursor !== null,
460
+ priceAggregation: {
461
+ minBound,
462
+ maxBound,
463
+ ranges: priceRanges
464
+ }
465
+ };
466
+ } catch (error) {
467
+ console.error("[wixStores.searchProducts] Search failed:", error);
468
+ throw new ActionError("SEARCH_FAILED", "Failed to search products");
469
+ }
470
+ }
471
+ );
472
+ const getProductBySlug = makeJayQuery("wixStores.getProductBySlug").withServices(WIX_STORES_SERVICE_MARKER).withCaching({ maxAge: 300, staleWhileRevalidate: 600 }).withHandler(
473
+ async (input, wixStores) => {
474
+ const { slug } = input;
475
+ if (!slug) {
476
+ throw new ActionError("INVALID_INPUT", "Product slug is required");
477
+ }
478
+ try {
479
+ const prefixConfig = wixStores.categoryPrefixes;
480
+ const fields = [
481
+ "MEDIA_ITEMS_INFO",
482
+ "VARIANT_OPTION_CHOICE_NAMES",
483
+ ...prefixConfig.length > 0 ? ["ALL_CATEGORIES_INFO"] : []
484
+ ];
485
+ const result = await wixStores.products.getProductBySlug(slug, {
486
+ fields: [...fields]
487
+ });
488
+ const product = result.product;
489
+ if (!product) {
490
+ return null;
491
+ }
492
+ return mapProductToCard(product, "/products", prefixConfig);
493
+ } catch (error) {
494
+ console.error("[wixStores.getProductBySlug] Failed to get product:", error);
495
+ return null;
496
+ }
497
+ }
498
+ );
499
+ const getCategories = makeJayQuery("wixStores.getCategories").withServices(WIX_STORES_SERVICE_MARKER).withCaching({ maxAge: 3600 }).withHandler(
500
+ async (_input, wixStores) => {
501
+ try {
502
+ const result = await wixStores.categories.queryCategories({
503
+ treeReference: {
504
+ appNamespace: "@wix/stores"
505
+ }
506
+ }).eq("visible", true).find();
507
+ return (result.items || []).map((cat) => ({
508
+ categoryId: cat._id || "",
509
+ categoryName: cat.name || ""
510
+ }));
511
+ } catch (error) {
512
+ console.error("[wixStores.getCategories] Failed to load categories:", error);
513
+ throw new ActionError("LOAD_FAILED", "Failed to load categories");
514
+ }
515
+ }
516
+ );
517
+ const PAGE_SIZE = 12;
518
+ async function renderSlowlyChanging$2(props, wixStores) {
519
+ const Pipeline = RenderPipeline.for();
520
+ const categoryPrefix = props.category;
521
+ const categoryConfig = categoryPrefix ? wixStores.categoryPrefixes.find((c) => c.prefix === categoryPrefix) : null;
522
+ const baseCategoryId = categoryConfig?.categoryId ?? null;
523
+ return Pipeline.try(async () => {
524
+ let query = wixStores.categories.queryCategories({
525
+ treeReference: { appNamespace: "@wix/stores" }
526
+ }).eq("visible", true);
527
+ if (baseCategoryId) {
528
+ query = query.eq("parentCategory.id", baseCategoryId);
529
+ }
530
+ const categoriesResult = await query.find();
531
+ return categoriesResult.items || [];
532
+ }).recover((error) => {
533
+ console.error("Failed to load categories:", error);
534
+ return Pipeline.ok([]);
535
+ }).toPhaseOutput((categories2) => {
536
+ const categoryInfos = categories2.map((cat) => ({
537
+ categoryId: cat._id || "",
538
+ categoryName: cat.name || "",
539
+ categorySlug: cat.slug || ""
540
+ }));
541
+ return {
542
+ viewState: {
543
+ searchFields: "name,description,sku",
544
+ fuzzySearch: true,
545
+ emptyStateMessage: "Enter a search term to find products",
546
+ filters: {
547
+ categoryFilter: {
548
+ categories: categoryInfos
549
+ }
550
+ }
551
+ },
552
+ carryForward: {
553
+ searchFields: "name,description,sku",
554
+ fuzzySearch: true,
555
+ categories: categoryInfos,
556
+ baseCategoryId
557
+ }
558
+ };
559
+ });
560
+ }
561
+ async function renderFastChanging$1(props, slowCarryForward, _wixStores) {
562
+ const Pipeline = RenderPipeline.for();
563
+ return Pipeline.try(async () => {
564
+ const baseCategoryIds = slowCarryForward.baseCategoryId ? [slowCarryForward.baseCategoryId] : [];
565
+ const result = await searchProducts({
566
+ query: "",
567
+ filters: baseCategoryIds.length > 0 ? { categoryIds: baseCategoryIds } : void 0,
568
+ pageSize: PAGE_SIZE
569
+ });
570
+ return result;
571
+ }).recover((error) => {
572
+ console.error("Failed to load products:", error);
573
+ return Pipeline.ok({
574
+ products: [],
575
+ totalCount: 0,
576
+ nextCursor: null,
577
+ hasMore: false,
578
+ priceAggregation: {
579
+ minBound: 0,
580
+ maxBound: 1e3,
581
+ ranges: [
582
+ {
583
+ rangeId: "all",
584
+ label: "Show all",
585
+ minValue: null,
586
+ maxValue: null,
587
+ productCount: 0,
588
+ isSelected: true
589
+ }
590
+ ]
591
+ }
592
+ });
593
+ }).toPhaseOutput((result) => {
594
+ const priceAgg = result.priceAggregation || {
595
+ minBound: 0,
596
+ maxBound: 1e3,
597
+ ranges: [
598
+ {
599
+ rangeId: "all",
600
+ label: "Show all",
601
+ minValue: null,
602
+ maxValue: null,
603
+ productCount: result.totalCount,
604
+ isSelected: true
605
+ }
606
+ ]
607
+ };
608
+ return {
609
+ viewState: {
610
+ searchExpression: "",
611
+ isSearching: false,
612
+ hasSearched: false,
613
+ searchResults: result.products,
614
+ resultCount: result.products.length,
615
+ hasResults: result.products.length > 0,
616
+ hasSuggestions: false,
617
+ suggestions: [],
618
+ filters: {
619
+ inStockOnly: false,
620
+ priceRange: {
621
+ // Initialize sliders to full range (bounds)
622
+ minPrice: priceAgg.minBound,
623
+ maxPrice: priceAgg.maxBound,
624
+ minBound: priceAgg.minBound,
625
+ maxBound: priceAgg.maxBound,
626
+ ranges: priceAgg.ranges
627
+ },
628
+ categoryFilter: {
629
+ categories: slowCarryForward.categories.map((cat) => ({
630
+ categoryId: cat.categoryId,
631
+ isSelected: false
632
+ }))
633
+ }
634
+ },
635
+ sortBy: {
636
+ currentSort: CurrentSort.relevance
637
+ },
638
+ hasMore: result.hasMore,
639
+ loadedCount: result.products.length,
640
+ totalCount: result.totalCount
641
+ },
642
+ carryForward: {
643
+ searchFields: slowCarryForward.searchFields,
644
+ fuzzySearch: slowCarryForward.fuzzySearch,
645
+ categories: slowCarryForward.categories,
646
+ baseCategoryId: slowCarryForward.baseCategoryId
647
+ }
648
+ };
649
+ });
650
+ }
651
+ const productSearch = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withSlowlyRender(renderSlowlyChanging$2).withFastRender(renderFastChanging$1);
652
+ var ProductType = /* @__PURE__ */ ((ProductType2) => {
653
+ ProductType2[ProductType2["PHYSICAL"] = 0] = "PHYSICAL";
654
+ ProductType2[ProductType2["DIGITAL"] = 1] = "DIGITAL";
655
+ return ProductType2;
656
+ })(ProductType || {});
657
+ var StockStatus = /* @__PURE__ */ ((StockStatus2) => {
658
+ StockStatus2[StockStatus2["OUT_OF_STOCK"] = 0] = "OUT_OF_STOCK";
659
+ StockStatus2[StockStatus2["IN_STOCK"] = 1] = "IN_STOCK";
660
+ return StockStatus2;
661
+ })(StockStatus || {});
662
+ var OptionRenderType = /* @__PURE__ */ ((OptionRenderType2) => {
663
+ OptionRenderType2[OptionRenderType2["TEXT_CHOICES"] = 0] = "TEXT_CHOICES";
664
+ OptionRenderType2[OptionRenderType2["COLOR_SWATCH_CHOICES"] = 1] = "COLOR_SWATCH_CHOICES";
665
+ return OptionRenderType2;
666
+ })(OptionRenderType || {});
667
+ var ChoiceType = /* @__PURE__ */ ((ChoiceType2) => {
668
+ ChoiceType2[ChoiceType2["CHOICE_TEXT"] = 0] = "CHOICE_TEXT";
669
+ ChoiceType2[ChoiceType2["ONE_COLOR"] = 1] = "ONE_COLOR";
670
+ return ChoiceType2;
671
+ })(ChoiceType || {});
672
+ var ModifierType = /* @__PURE__ */ ((ModifierType2) => {
673
+ ModifierType2[ModifierType2["TEXT_CHOICES"] = 0] = "TEXT_CHOICES";
674
+ ModifierType2[ModifierType2["COLOR_SWATCH_CHOICES"] = 1] = "COLOR_SWATCH_CHOICES";
675
+ ModifierType2[ModifierType2["FREE_TEXT"] = 2] = "FREE_TEXT";
676
+ return ModifierType2;
677
+ })(ModifierType || {});
678
+ var ChoiceType = /* @__PURE__ */ ((ChoiceType2) => {
679
+ ChoiceType2[ChoiceType2["CHOICE_TEXT"] = 0] = "CHOICE_TEXT";
680
+ ChoiceType2[ChoiceType2["ONE_COLOR"] = 1] = "ONE_COLOR";
681
+ return ChoiceType2;
682
+ })(ChoiceType || {});
683
+ var Selected = /* @__PURE__ */ ((Selected2) => {
684
+ Selected2[Selected2["selected"] = 0] = "selected";
685
+ Selected2[Selected2["notSelected"] = 1] = "notSelected";
686
+ return Selected2;
687
+ })(Selected || {});
688
+ var MediaType = /* @__PURE__ */ ((MediaType2) => {
689
+ MediaType2[MediaType2["IMAGE"] = 0] = "IMAGE";
690
+ MediaType2[MediaType2["VIDEO"] = 1] = "VIDEO";
691
+ return MediaType2;
692
+ })(MediaType || {});
693
+ async function* loadProductParams([wixStores]) {
694
+ const prefixConfig = wixStores.categoryPrefixes;
695
+ const hasPrefixes = prefixConfig.length > 0;
696
+ const fields = hasPrefixes ? ["ALL_CATEGORIES_INFO"] : [];
697
+ try {
698
+ let result = await wixStores.products.queryProducts({ fields: [...fields] }).find();
699
+ yield result.items.map((product) => mapProductToParams(product, prefixConfig));
700
+ while (result.hasNext()) {
701
+ result = await result.next();
702
+ yield result.items.map((product) => mapProductToParams(product, prefixConfig));
703
+ }
704
+ } catch (error) {
705
+ console.error("Failed to load product slugs:", error);
706
+ yield [];
707
+ }
708
+ }
709
+ function mapProductToParams(product, prefixConfig) {
710
+ const prefix = resolveProductPrefix(product, prefixConfig);
711
+ return {
712
+ slug: product.slug ?? "",
713
+ ...prefix ? { category: prefix } : {}
714
+ };
715
+ }
716
+ function mapProductType(productType) {
717
+ return productType === "DIGITAL" ? ProductType.DIGITAL : ProductType.PHYSICAL;
718
+ }
719
+ function mapInfoSections(infoSections) {
720
+ return infoSections.map((infoSection) => ({
721
+ _id: infoSection._id,
722
+ plainDescription: infoSection.plainDescription || "",
723
+ title: infoSection.title || "",
724
+ uniqueName: infoSection.uniqueName || ""
725
+ }));
726
+ }
727
+ function mapSeoData(seoData) {
728
+ return {
729
+ tags: seoData?.tags?.map((tag, index) => ({
730
+ position: index.toString().padStart(2, "0"),
731
+ type: tag.type,
732
+ props: Object.entries(tag.props || {}).map(([key, value]) => ({ key, value })),
733
+ meta: Object.entries(tag.meta || {}).map(([key, value]) => ({ key, value })),
734
+ children: tag.children
735
+ })),
736
+ settings: {
737
+ preventAutoRedirect: seoData?.settings?.preventAutoRedirect || false,
738
+ keywords: seoData?.settings?.keywords.map((keyword) => ({
739
+ isMain: keyword.isMain,
740
+ origin: keyword.origin,
741
+ term: keyword.term
742
+ }))
743
+ }
744
+ };
745
+ }
746
+ function mapMediaType(mediaType) {
747
+ if (mediaType === "VIDEO")
748
+ return MediaType.VIDEO;
749
+ else
750
+ return MediaType.IMAGE;
751
+ }
752
+ function mapMedia(media) {
753
+ const mainMediaType = mapMediaType(media.main.mediaType);
754
+ return {
755
+ selectedMedia: {
756
+ url: formatWixMediaUrl(media.main._id, media.main.url),
757
+ mediaType: mainMediaType,
758
+ thumbnail_50x50: formatWixMediaUrl(media.main._id, media.main.url, { w: 50, h: 50 })
759
+ },
760
+ availableMedia: media.itemsInfo?.items?.map((item) => ({
761
+ mediaId: item._id,
762
+ media: {
763
+ url: formatWixMediaUrl(item._id, item.url),
764
+ mediaType: item.mediaType === "IMAGE" ? MediaType.IMAGE : MediaType.VIDEO,
765
+ thumbnail_50x50: formatWixMediaUrl(item._id, item.url, { w: 50, h: 50 })
766
+ },
767
+ selected: item._id === media.main._id ? Selected.selected : Selected.notSelected
768
+ })) ?? []
769
+ };
770
+ }
771
+ function mapOptionsToSlowVS(options) {
772
+ return options?.map((option) => ({
773
+ name: option.name,
774
+ optionRenderType: option.optionRenderType === "TEXT_CHOICES" ? OptionRenderType.TEXT_CHOICES : OptionRenderType.COLOR_SWATCH_CHOICES,
775
+ _id: option._id,
776
+ choices: option.choicesSettings?.choices?.map((choice) => ({
777
+ name: choice.name,
778
+ choiceId: choice.choiceId,
779
+ choiceType: choice.choiceType === "CHOICE_TEXT" ? ChoiceType.CHOICE_TEXT : ChoiceType.ONE_COLOR,
780
+ inStock: choice.inStock,
781
+ colorCode: choice.colorCode
782
+ })) ?? []
783
+ })) ?? [];
784
+ }
785
+ function mapOptionsToFastVS(options) {
786
+ return options?.map((option) => ({
787
+ _id: option._id,
788
+ textChoiceSelection: void 0,
789
+ choices: option.choicesSettings?.choices?.map((choice) => ({
790
+ choiceId: choice.choiceId,
791
+ isSelected: false
792
+ })) ?? []
793
+ })) ?? [];
794
+ }
795
+ function mapModifierType(modifierRenderType) {
796
+ switch (modifierRenderType) {
797
+ case "FREE_TEXT":
798
+ return ModifierType.FREE_TEXT;
799
+ case "TEXT_CHOICES":
800
+ return ModifierType.TEXT_CHOICES;
801
+ case "SWATCH_CHOICES":
802
+ return ModifierType.COLOR_SWATCH_CHOICES;
803
+ default:
804
+ return ModifierType.FREE_TEXT;
805
+ }
806
+ }
807
+ function mapModifierChoiceType(choiceType) {
808
+ if (choiceType === "ONE_COLOR")
809
+ return ChoiceType.ONE_COLOR;
810
+ else
811
+ return ChoiceType.CHOICE_TEXT;
812
+ }
813
+ function mapModifiersToSlowVS(modifiers) {
814
+ return modifiers?.map((modifier) => ({
815
+ name: modifier.name || modifier.freeTextSettings?.title || "",
816
+ _id: modifier._id,
817
+ modifierType: mapModifierType(modifier.modifierRenderType),
818
+ textInputLength: modifier.freeTextSettings?.maxCharCount,
819
+ textInputRequired: modifier.mandatory,
820
+ choices: modifier.choicesSettings?.choices?.map((choice) => ({
821
+ name: choice.name,
822
+ choiceId: choice.choiceId,
823
+ colorCode: choice.colorCode,
824
+ choiceType: mapModifierChoiceType(choice.choiceType)
825
+ })) ?? []
826
+ })) ?? [];
827
+ }
828
+ function mapModifiersToFastVS(modifiers) {
829
+ return modifiers?.map((modifier) => ({
830
+ _id: modifier._id,
831
+ textModifierSelection: void 0,
832
+ choices: modifier.choicesSettings?.choices?.map((choice) => ({
833
+ choiceId: choice.choiceId,
834
+ isSelected: false
835
+ })) ?? []
836
+ })) ?? [];
837
+ }
838
+ function mapVariants(variantsInfo) {
839
+ return variantsInfo?.variants.map((variant) => ({
840
+ _id: variant._id,
841
+ choices: variant.choices,
842
+ sku: variant.sku,
843
+ price: variant.price.actualPrice.formattedAmount,
844
+ inventoryStatus: variant.inventoryStatus.inStock ? StockStatus.IN_STOCK : StockStatus.OUT_OF_STOCK,
845
+ mediaId: variant.media?._id,
846
+ strikethroughPrice: variant.price.compareAtPrice?.formattedAmount || ""
847
+ })) || [];
848
+ }
849
+ async function renderSlowlyChanging$1(props, wixStores) {
850
+ const Pipeline = RenderPipeline.for();
851
+ const prefixConfig = wixStores.categoryPrefixes;
852
+ const hasPrefixes = prefixConfig.length > 0;
853
+ return Pipeline.try(async () => {
854
+ const fields = [
855
+ "INFO_SECTION",
856
+ "INFO_SECTION_PLAIN_DESCRIPTION",
857
+ "MEDIA_ITEMS_INFO",
858
+ "PLAIN_DESCRIPTION",
859
+ "CURRENCY",
860
+ ...hasPrefixes ? ["ALL_CATEGORIES_INFO"] : []
861
+ ];
862
+ const response = await wixStores.products.getProductBySlug(props.slug, {
863
+ fields: [...fields]
864
+ });
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
+ return response;
872
+ }).recover((error) => {
873
+ console.log("product page error", error);
874
+ return Pipeline.clientError(404, "not found");
875
+ }).toPhaseOutput((getProductResponse) => {
876
+ const product = getProductResponse.product;
877
+ const { _id, name, plainDescription, options, modifiers, actualPriceRange, compareAtPriceRange, media, productType, brand, ribbon, infoSections, seoData, physicalProperties, inventory, variantsInfo } = product;
878
+ return {
879
+ viewState: {
880
+ _id,
881
+ productName: name || "",
882
+ description: plainDescription,
883
+ brand: brand?.name || "",
884
+ ribbon: ribbon?.name || "",
885
+ productType: mapProductType(productType),
886
+ options: mapOptionsToSlowVS(options),
887
+ infoSections: mapInfoSections(infoSections),
888
+ modifiers: mapModifiersToSlowVS(modifiers),
889
+ seoData: mapSeoData(seoData)
890
+ },
891
+ carryForward: {
892
+ productId: _id,
893
+ mediaGallery: mapMedia(media),
894
+ options: mapOptionsToFastVS(options),
895
+ modifiers: mapModifiersToFastVS(modifiers),
896
+ sku: "N/A not in API",
897
+ price: actualPriceRange?.minValue?.formattedAmount || "",
898
+ strikethroughPrice: actualPriceRange?.minValue?.amount !== product.compareAtPriceRange?.minValue?.amount ? compareAtPriceRange?.minValue?.formattedAmount || "" : "",
899
+ pricePerUnit: physicalProperties?.pricePerUnitRange?.minValue?.description,
900
+ stockStatus: inventory?.availabilityStatus === "IN_STOCK" ? StockStatus.IN_STOCK : StockStatus.OUT_OF_STOCK,
901
+ variants: mapVariants(variantsInfo)
902
+ }
903
+ };
904
+ });
905
+ }
906
+ async function renderFastChanging(props, slowCarryForward, wixStores) {
907
+ const Pipeline = RenderPipeline.for();
908
+ const isInStock = slowCarryForward.stockStatus === StockStatus.IN_STOCK;
909
+ return Pipeline.ok({
910
+ actionsEnabled: isInStock,
911
+ options: slowCarryForward.options,
912
+ modifiers: slowCarryForward.modifiers,
913
+ mediaGallery: slowCarryForward.mediaGallery,
914
+ sku: slowCarryForward.variants[0].sku,
915
+ price: slowCarryForward.variants[0].price,
916
+ pricePerUnit: slowCarryForward.pricePerUnit || "",
917
+ stockStatus: slowCarryForward.stockStatus,
918
+ strikethroughPrice: slowCarryForward.variants[0].strikethroughPrice,
919
+ quantity: { quantity: 1 }
920
+ }).toPhaseOutput((viewState) => ({
921
+ viewState,
922
+ carryForward: {
923
+ productId: slowCarryForward.productId,
924
+ variants: slowCarryForward.variants
925
+ }
926
+ }));
927
+ }
928
+ const productPage = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withLoadParams(loadProductParams).withSlowlyRender(renderSlowlyChanging$1).withFastRender(renderFastChanging);
929
+ async function renderSlowlyChanging(props, wixStores) {
930
+ const Pipeline = RenderPipeline.for();
931
+ return Pipeline.try(async () => {
932
+ const result = await wixStores.categories.queryCategories({
933
+ treeReference: {
934
+ appNamespace: "@wix/stores"
935
+ }
936
+ }).eq("visible", true).find();
937
+ return result.items || [];
938
+ }).recover((error) => {
939
+ console.error("Failed to load categories:", error);
940
+ return Pipeline.ok([]);
941
+ }).toPhaseOutput((categories2) => {
942
+ const categoryItems = categories2.map((cat) => ({
943
+ _id: cat._id || "",
944
+ name: cat.name || "",
945
+ slug: cat.slug || "",
946
+ description: cat.description || "",
947
+ productCount: cat.itemCounter || 0,
948
+ imageUrl: cat.media?.mainMedia?.url || ""
949
+ }));
950
+ return {
951
+ viewState: {
952
+ categories: categoryItems,
953
+ hasCategories: categoryItems.length > 0
954
+ },
955
+ carryForward: {}
956
+ };
957
+ });
958
+ }
959
+ const categoryList = makeJayStackComponent().withProps().withServices(WIX_STORES_SERVICE_MARKER).withSlowlyRender(renderSlowlyChanging);
960
+ const WIX_STORES_CONTEXT = createJayContext();
961
+ function loadWixStoresConfig() {
962
+ const configPath = path.join(process.cwd(), "config", ".wix-stores.yaml");
963
+ if (!fs.existsSync(configPath)) {
964
+ return { categoryPrefixes: [] };
965
+ }
966
+ const fileContents = fs.readFileSync(configPath, "utf8");
967
+ const raw = yaml.load(fileContents);
968
+ if (!raw) {
969
+ return { categoryPrefixes: [] };
970
+ }
971
+ const prefixes = [];
972
+ if (Array.isArray(raw.categoryPrefixes)) {
973
+ for (const entry of raw.categoryPrefixes) {
974
+ if (typeof entry.categoryId === "string" && typeof entry.prefix === "string") {
975
+ prefixes.push({
976
+ categoryId: entry.categoryId.trim(),
977
+ prefix: entry.prefix.trim(),
978
+ name: typeof entry.name === "string" ? entry.name.trim() : entry.prefix.trim()
979
+ });
980
+ }
981
+ }
982
+ }
983
+ return { categoryPrefixes: prefixes };
984
+ }
985
+ const init = makeJayInit().withServer(async () => {
986
+ console.log("[wix-stores] Initializing Wix Stores service...");
987
+ const wixClient = getService(WIX_CLIENT_SERVICE);
988
+ const storesConfig = loadWixStoresConfig();
989
+ provideWixStoresService(wixClient, {
990
+ categoryPrefixes: storesConfig.categoryPrefixes
991
+ });
992
+ if (storesConfig.categoryPrefixes.length > 0) {
993
+ console.log(`[wix-stores] Category prefixes configured: ${storesConfig.categoryPrefixes.map((p) => p.prefix).join(", ")}`);
994
+ }
995
+ console.log("[wix-stores] Server initialization complete");
996
+ return {
997
+ enableClientCart: true,
998
+ enableClientSearch: true
999
+ };
1000
+ });
1001
+ const CONFIG_FILE_NAME = ".wix-stores.yaml";
1002
+ const CONFIG_TEMPLATE = `# Wix Stores Configuration
1003
+ #
1004
+ # Category Prefixes (optional):
1005
+ # Maps root Wix categories to URL prefix slugs.
1006
+ # Products under a root category get URLs like /products/{prefix}/{product-slug}
1007
+ # Each prefix gets its own search/listing page and product page templates.
1008
+ #
1009
+ # To find category IDs, use: jay-stack action wix-stores/getCategories
1010
+ #
1011
+ # categoryPrefixes:
1012
+ # - categoryId: "<root-category-id>"
1013
+ # prefix: "<url-prefix>"
1014
+ # name: "<display-name>"
1015
+ # - categoryId: "<another-root-category-id>"
1016
+ # prefix: "<another-url-prefix>"
1017
+ # name: "<another-display-name>"
1018
+ `;
1019
+ async function setupWixStores(ctx) {
1020
+ if (ctx.initError) {
1021
+ return {
1022
+ status: "error",
1023
+ message: `Service init failed (is wix-server-client configured?). ${ctx.initError.message}`
1024
+ };
1025
+ }
1026
+ try {
1027
+ getService(WIX_STORES_SERVICE_MARKER);
1028
+ } catch {
1029
+ return {
1030
+ status: "error",
1031
+ message: "WixStoresService not available. Run setup for wix-server-client first."
1032
+ };
1033
+ }
1034
+ const configPath = path.join(ctx.configDir, CONFIG_FILE_NAME);
1035
+ const configCreated = [];
1036
+ if (!fs.existsSync(configPath)) {
1037
+ if (!fs.existsSync(ctx.configDir)) {
1038
+ fs.mkdirSync(ctx.configDir, { recursive: true });
1039
+ }
1040
+ fs.writeFileSync(configPath, CONFIG_TEMPLATE, "utf-8");
1041
+ configCreated.push(`config/${CONFIG_FILE_NAME}`);
1042
+ }
1043
+ const service = getService(WIX_STORES_SERVICE_MARKER);
1044
+ const prefixCount = service.categoryPrefixes.length;
1045
+ const message = prefixCount > 0 ? `Wix Stores configured with ${prefixCount} category prefix(es): ${service.categoryPrefixes.map((p) => p.prefix).join(", ")}` : "Wix Stores service verified";
1046
+ return {
1047
+ status: "configured",
1048
+ message,
1049
+ ...configCreated.length > 0 ? { configCreated } : {}
1050
+ };
1051
+ }
1052
+ async function generateWixStoresReferences(ctx) {
1053
+ if (ctx.initError) {
1054
+ throw new Error(`init failed: ${ctx.initError.message}`);
1055
+ }
1056
+ let storesService;
1057
+ try {
1058
+ storesService = getService(WIX_STORES_SERVICE_MARKER);
1059
+ } catch {
1060
+ throw new Error("WixStoresService not available. Run jay-stack setup first.");
1061
+ }
1062
+ fs.mkdirSync(ctx.referencesDir, { recursive: true });
1063
+ const allCategories = [];
1064
+ let result = await storesService.categories.queryCategories({
1065
+ treeReference: { appNamespace: "@wix/stores" }
1066
+ }).eq("visible", true).limit(100).find();
1067
+ allCategories.push(...result.items || []);
1068
+ while (result.hasNext()) {
1069
+ result = await result.next();
1070
+ allCategories.push(...result.items || []);
1071
+ }
1072
+ const nodeMap = /* @__PURE__ */ new Map();
1073
+ for (const cat of allCategories) {
1074
+ if (!cat._id) continue;
1075
+ nodeMap.set(cat._id, {
1076
+ _id: cat._id,
1077
+ name: cat.name || "",
1078
+ slug: cat.slug || "",
1079
+ productCount: cat.itemCounter || 0,
1080
+ children: []
1081
+ });
1082
+ }
1083
+ const roots = [];
1084
+ for (const cat of allCategories) {
1085
+ if (!cat._id) continue;
1086
+ const node = nodeMap.get(cat._id);
1087
+ const parentId = cat.parentCategory?._id;
1088
+ if (parentId && nodeMap.has(parentId)) {
1089
+ nodeMap.get(parentId).children.push(node);
1090
+ } else {
1091
+ roots.push(node);
1092
+ }
1093
+ }
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
+ const categoriesPath = path.join(ctx.referencesDir, "categories.yaml");
1102
+ fs.writeFileSync(
1103
+ categoriesPath,
1104
+ yaml.dump(
1105
+ {
1106
+ _generated: (/* @__PURE__ */ new Date()).toISOString(),
1107
+ _description: "Wix Stores category tree for agent discovery. Shows category hierarchy, IDs, product counts, and configured URL prefixes.",
1108
+ totalCategories: allCategories.length,
1109
+ configuredPrefixes: configuredPrefixes.length > 0 ? configuredPrefixes : void 0,
1110
+ categoryTree: roots
1111
+ },
1112
+ { indent: 2, lineWidth: 120, noRefs: true }
1113
+ ),
1114
+ "utf-8"
1115
+ );
1116
+ return {
1117
+ referencesCreated: [`agent-kit/references/${ctx.pluginName}/categories.yaml`],
1118
+ message: `${allCategories.length} categories (${roots.length} root)`
1119
+ };
1120
+ }
1121
+ export {
1122
+ WIX_CART_CONTEXT,
1123
+ WIX_CART_SERVICE,
1124
+ WIX_STORES_CONTEXT,
1125
+ WIX_STORES_SERVICE_MARKER,
1126
+ cartIndicator,
1127
+ cartPage,
1128
+ categoryList,
1129
+ generateWixStoresReferences,
1130
+ getCategories,
1131
+ getProductBySlug,
1132
+ init,
1133
+ productPage,
1134
+ productSearch,
1135
+ provideWixCartContext,
1136
+ provideWixCartService,
1137
+ provideWixStoresService,
1138
+ searchProducts,
1139
+ setupWixStores
1140
+ };