@laioutr/app-shopware 0.14.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.
Files changed (193) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +146 -0
  3. package/dist/module.d.mts +88 -0
  4. package/dist/module.json +9 -0
  5. package/dist/module.mjs +60 -0
  6. package/dist/runtime/app/components/ShopwareEmbedFrame.d.vue.ts +14 -0
  7. package/dist/runtime/app/components/ShopwareEmbedFrame.vue +57 -0
  8. package/dist/runtime/app/components/ShopwareEmbedFrame.vue.d.ts +14 -0
  9. package/dist/runtime/app/composables/useShopwareEmbedBridge.d.ts +24 -0
  10. package/dist/runtime/app/composables/useShopwareEmbedBridge.js +22 -0
  11. package/dist/runtime/app/const/bridge.d.ts +63 -0
  12. package/dist/runtime/app/const/bridge.js +9 -0
  13. package/dist/runtime/app/image/providers/shopware.d.ts +7 -0
  14. package/dist/runtime/app/image/providers/shopware.js +44 -0
  15. package/dist/runtime/app/lib/createBridgeHandler.d.ts +44 -0
  16. package/dist/runtime/app/lib/createBridgeHandler.js +40 -0
  17. package/dist/runtime/app/lib/isTrustedOrigin.d.ts +9 -0
  18. package/dist/runtime/app/lib/isTrustedOrigin.js +8 -0
  19. package/dist/runtime/app/lib/parseBridgeMessage.d.ts +11 -0
  20. package/dist/runtime/app/lib/parseBridgeMessage.js +34 -0
  21. package/dist/runtime/app/plugins/pagetypes.d.ts +7 -0
  22. package/dist/runtime/app/plugins/pagetypes.js +6 -0
  23. package/dist/runtime/app/public/app-shopware/image-placeholder.svg +7 -0
  24. package/dist/runtime/app/public/app-shopware/shopware-logo.svg +10 -0
  25. package/dist/runtime/app/sections/SectionShopwareCheckout.d.vue.ts +0 -0
  26. package/dist/runtime/app/sections/SectionShopwareCheckout.vue +44 -0
  27. package/dist/runtime/app/sections/SectionShopwareCheckout.vue.d.ts +0 -0
  28. package/dist/runtime/index.d.ts +1 -0
  29. package/dist/runtime/index.js +1 -0
  30. package/dist/runtime/server/client/shopwareAdminClientFactory.d.ts +4 -0
  31. package/dist/runtime/server/client/shopwareAdminClientFactory.js +13 -0
  32. package/dist/runtime/server/client/shopwareClientFactory.d.ts +3 -0
  33. package/dist/runtime/server/client/shopwareClientFactory.js +18 -0
  34. package/dist/runtime/server/composable/useGetProductParentId.d.ts +4 -0
  35. package/dist/runtime/server/composable/useGetProductParentId.js +41 -0
  36. package/dist/runtime/server/const/checkout.d.ts +7 -0
  37. package/dist/runtime/server/const/checkout.js +2 -0
  38. package/dist/runtime/server/const/cookieKeys.d.ts +1 -0
  39. package/dist/runtime/server/const/cookieKeys.js +1 -0
  40. package/dist/runtime/server/const/fallbacks.d.ts +5 -0
  41. package/dist/runtime/server/const/fallbacks.js +14 -0
  42. package/dist/runtime/server/const/includes.d.ts +1 -0
  43. package/dist/runtime/server/const/includes.js +1 -0
  44. package/dist/runtime/server/const/passthroughTokens.d.ts +14 -0
  45. package/dist/runtime/server/const/passthroughTokens.js +10 -0
  46. package/dist/runtime/server/media-libraries/shopware.d.ts +20 -0
  47. package/dist/runtime/server/media-libraries/shopware.js +90 -0
  48. package/dist/runtime/server/middleware/defineShopware.d.ts +7 -0
  49. package/dist/runtime/server/middleware/defineShopware.js +33 -0
  50. package/dist/runtime/server/orchestr/auth/login.action.d.ts +2 -0
  51. package/dist/runtime/server/orchestr/auth/login.action.js +17 -0
  52. package/dist/runtime/server/orchestr/cart/add-item.action.d.ts +2 -0
  53. package/dist/runtime/server/orchestr/cart/add-item.action.js +46 -0
  54. package/dist/runtime/server/orchestr/cart/base.resolver.d.ts +2 -0
  55. package/dist/runtime/server/orchestr/cart/base.resolver.js +28 -0
  56. package/dist/runtime/server/orchestr/cart/cart-items.link.d.ts +2 -0
  57. package/dist/runtime/server/orchestr/cart/cart-items.link.js +10 -0
  58. package/dist/runtime/server/orchestr/cart/get-checkout-url.action.d.ts +10 -0
  59. package/dist/runtime/server/orchestr/cart/get-checkout-url.action.js +6 -0
  60. package/dist/runtime/server/orchestr/cart/get-current.query.d.ts +2 -0
  61. package/dist/runtime/server/orchestr/cart/get-current.query.js +9 -0
  62. package/dist/runtime/server/orchestr/cart/remove-items.action.d.ts +2 -0
  63. package/dist/runtime/server/orchestr/cart/remove-items.action.js +14 -0
  64. package/dist/runtime/server/orchestr/cart/update-items.action.d.ts +2 -0
  65. package/dist/runtime/server/orchestr/cart/update-items.action.js +16 -0
  66. package/dist/runtime/server/orchestr/cart-item/base.resolver.d.ts +2 -0
  67. package/dist/runtime/server/orchestr/cart-item/base.resolver.js +33 -0
  68. package/dist/runtime/server/orchestr/category/all.query.d.ts +2 -0
  69. package/dist/runtime/server/orchestr/category/all.query.js +15 -0
  70. package/dist/runtime/server/orchestr/category/base.resolver.d.ts +2 -0
  71. package/dist/runtime/server/orchestr/category/base.resolver.js +47 -0
  72. package/dist/runtime/server/orchestr/category/bySlug.query.d.ts +2 -0
  73. package/dist/runtime/server/orchestr/category/bySlug.query.js +12 -0
  74. package/dist/runtime/server/orchestr/category/child-categories.link.d.ts +2 -0
  75. package/dist/runtime/server/orchestr/category/child-categories.link.js +36 -0
  76. package/dist/runtime/server/orchestr/category/listing-page.page-index.d.ts +2 -0
  77. package/dist/runtime/server/orchestr/category/listing-page.page-index.js +79 -0
  78. package/dist/runtime/server/orchestr/category/menu-item.link.d.ts +2 -0
  79. package/dist/runtime/server/orchestr/category/menu-item.link.js +10 -0
  80. package/dist/runtime/server/orchestr/menu/base.resolver.d.ts +2 -0
  81. package/dist/runtime/server/orchestr/menu/base.resolver.js +63 -0
  82. package/dist/runtime/server/orchestr/menu/byAlias.query.d.ts +2 -0
  83. package/dist/runtime/server/orchestr/menu/byAlias.query.js +29 -0
  84. package/dist/runtime/server/orchestr/menu/byAlias.template.d.ts +2 -0
  85. package/dist/runtime/server/orchestr/menu/byAlias.template.js +25 -0
  86. package/dist/runtime/server/orchestr/newsletter/subscribe.action.d.ts +2 -0
  87. package/dist/runtime/server/orchestr/newsletter/subscribe.action.js +27 -0
  88. package/dist/runtime/server/orchestr/product/base.resolver.d.ts +2 -0
  89. package/dist/runtime/server/orchestr/product/base.resolver.js +119 -0
  90. package/dist/runtime/server/orchestr/product/breadcrumb.link.d.ts +2 -0
  91. package/dist/runtime/server/orchestr/product/breadcrumb.link.js +57 -0
  92. package/dist/runtime/server/orchestr/product/byCategoryId.query.d.ts +2 -0
  93. package/dist/runtime/server/orchestr/product/byCategoryId.query.js +47 -0
  94. package/dist/runtime/server/orchestr/product/byCategorySlug.query.d.ts +2 -0
  95. package/dist/runtime/server/orchestr/product/byCategorySlug.query.js +59 -0
  96. package/dist/runtime/server/orchestr/product/byCategorySlug.template.d.ts +2 -0
  97. package/dist/runtime/server/orchestr/product/byCategorySlug.template.js +40 -0
  98. package/dist/runtime/server/orchestr/product/bySlug.query.d.ts +2 -0
  99. package/dist/runtime/server/orchestr/product/bySlug.query.js +19 -0
  100. package/dist/runtime/server/orchestr/product/detail-page.page-index.d.ts +3 -0
  101. package/dist/runtime/server/orchestr/product/detail-page.page-index.js +58 -0
  102. package/dist/runtime/server/orchestr/product/reviews.link.d.ts +2 -0
  103. package/dist/runtime/server/orchestr/product/reviews.link.js +27 -0
  104. package/dist/runtime/server/orchestr/product/search.query.d.ts +2 -0
  105. package/dist/runtime/server/orchestr/product/search.query.js +43 -0
  106. package/dist/runtime/server/orchestr/product/variants.link.d.ts +2 -0
  107. package/dist/runtime/server/orchestr/product/variants.link.js +18 -0
  108. package/dist/runtime/server/orchestr/product-variant/base.resolver.d.ts +2 -0
  109. package/dist/runtime/server/orchestr/product-variant/base.resolver.js +129 -0
  110. package/dist/runtime/server/orchestr/review/base.resolver.d.ts +2 -0
  111. package/dist/runtime/server/orchestr/review/base.resolver.js +38 -0
  112. package/dist/runtime/server/orchestr/review/create-review.action.d.ts +2 -0
  113. package/dist/runtime/server/orchestr/review/create-review.action.js +14 -0
  114. package/dist/runtime/server/orchestr/suggested-search/suggested-search-entry.link.d.ts +2 -0
  115. package/dist/runtime/server/orchestr/suggested-search/suggested-search-entry.link.js +14 -0
  116. package/dist/runtime/server/orchestr/suggested-search/suggested-search.query.d.ts +2 -0
  117. package/dist/runtime/server/orchestr/suggested-search/suggested-search.query.js +54 -0
  118. package/dist/runtime/server/orchestr/suggested-search-entry/base.resolver.d.ts +2 -0
  119. package/dist/runtime/server/orchestr/suggested-search-entry/base.resolver.js +22 -0
  120. package/dist/runtime/server/orchestr-helper/isLinkSingle.d.ts +2 -0
  121. package/dist/runtime/server/orchestr-helper/isLinkSingle.js +1 -0
  122. package/dist/runtime/server/orchestr-helper/matchAndMap.d.ts +1 -0
  123. package/dist/runtime/server/orchestr-helper/matchAndMap.js +15 -0
  124. package/dist/runtime/server/orchestr-helper/requestedFields.d.ts +18 -0
  125. package/dist/runtime/server/orchestr-helper/requestedFields.js +145 -0
  126. package/dist/runtime/server/routes/adopt-session.post.d.ts +8 -0
  127. package/dist/runtime/server/routes/adopt-session.post.js +16 -0
  128. package/dist/runtime/server/routes/checkout.d.ts +16 -0
  129. package/dist/runtime/server/routes/checkout.js +31 -0
  130. package/dist/runtime/server/shopware-helper/adoptSession.d.ts +18 -0
  131. package/dist/runtime/server/shopware-helper/adoptSession.js +23 -0
  132. package/dist/runtime/server/shopware-helper/bootstrapContextToken.d.ts +18 -0
  133. package/dist/runtime/server/shopware-helper/bootstrapContextToken.js +22 -0
  134. package/dist/runtime/server/shopware-helper/cartErrors.d.ts +11 -0
  135. package/dist/runtime/server/shopware-helper/cartErrors.js +21 -0
  136. package/dist/runtime/server/shopware-helper/cartMapper.d.ts +14 -0
  137. package/dist/runtime/server/shopware-helper/cartMapper.js +83 -0
  138. package/dist/runtime/server/shopware-helper/categoryFlattener.d.ts +75 -0
  139. package/dist/runtime/server/shopware-helper/categoryFlattener.js +11 -0
  140. package/dist/runtime/server/shopware-helper/checkoutUrl.d.ts +12 -0
  141. package/dist/runtime/server/shopware-helper/checkoutUrl.js +5 -0
  142. package/dist/runtime/server/shopware-helper/contextTokenCookie.d.ts +11 -0
  143. package/dist/runtime/server/shopware-helper/contextTokenCookie.js +7 -0
  144. package/dist/runtime/server/shopware-helper/facetMapper.d.ts +38 -0
  145. package/dist/runtime/server/shopware-helper/facetMapper.js +79 -0
  146. package/dist/runtime/server/shopware-helper/fetchAllProductVariants.d.ts +137 -0
  147. package/dist/runtime/server/shopware-helper/fetchAllProductVariants.js +16 -0
  148. package/dist/runtime/server/shopware-helper/getCart.d.ts +10 -0
  149. package/dist/runtime/server/shopware-helper/getCart.js +4 -0
  150. package/dist/runtime/server/shopware-helper/isShopwareId.d.ts +4 -0
  151. package/dist/runtime/server/shopware-helper/isShopwareId.js +2 -0
  152. package/dist/runtime/server/shopware-helper/mappers/slugMapper.d.ts +10 -0
  153. package/dist/runtime/server/shopware-helper/mappers/slugMapper.js +9 -0
  154. package/dist/runtime/server/shopware-helper/mediaMapper.d.ts +8 -0
  155. package/dist/runtime/server/shopware-helper/mediaMapper.js +42 -0
  156. package/dist/runtime/server/shopware-helper/pageIndexEntries.d.ts +20 -0
  157. package/dist/runtime/server/shopware-helper/pageIndexEntries.js +22 -0
  158. package/dist/runtime/server/shopware-helper/persistContextToken.d.ts +13 -0
  159. package/dist/runtime/server/shopware-helper/persistContextToken.js +12 -0
  160. package/dist/runtime/server/shopware-helper/productLocate.d.ts +14 -0
  161. package/dist/runtime/server/shopware-helper/productLocate.js +9 -0
  162. package/dist/runtime/server/shopware-helper/readCategoryPageMeta.d.ts +10 -0
  163. package/dist/runtime/server/shopware-helper/readCategoryPageMeta.js +19 -0
  164. package/dist/runtime/server/shopware-helper/readProductPageMeta.d.ts +10 -0
  165. package/dist/runtime/server/shopware-helper/readProductPageMeta.js +20 -0
  166. package/dist/runtime/server/shopware-helper/resolveAdoptSession.d.ts +23 -0
  167. package/dist/runtime/server/shopware-helper/resolveAdoptSession.js +13 -0
  168. package/dist/runtime/server/shopware-helper/resolveCheckout.d.ts +39 -0
  169. package/dist/runtime/server/shopware-helper/resolveCheckout.js +24 -0
  170. package/dist/runtime/server/shopware-helper/sessionHandoff.d.ts +27 -0
  171. package/dist/runtime/server/shopware-helper/sessionHandoff.js +29 -0
  172. package/dist/runtime/server/shopware-helper/sortingMapper.d.ts +3 -0
  173. package/dist/runtime/server/shopware-helper/sortingMapper.js +7 -0
  174. package/dist/runtime/server/shopware-helper/storeApiPageFetcher.d.ts +20 -0
  175. package/dist/runtime/server/shopware-helper/storeApiPageFetcher.js +10 -0
  176. package/dist/runtime/server/shopware-helper/swTranslated.d.ts +7 -0
  177. package/dist/runtime/server/shopware-helper/swTranslated.js +4 -0
  178. package/dist/runtime/server/shopware-helper/system/getCurrentSystemEntities.d.ts +20 -0
  179. package/dist/runtime/server/shopware-helper/system/getCurrentSystemEntities.js +39 -0
  180. package/dist/runtime/server/shopware-helper/system/getSystemEntities.d.ts +31 -0
  181. package/dist/runtime/server/shopware-helper/system/getSystemEntities.js +47 -0
  182. package/dist/runtime/server/shopware-helper/useSeoResolver.d.ts +11 -0
  183. package/dist/runtime/server/shopware-helper/useSeoResolver.js +70 -0
  184. package/dist/runtime/server/tsconfig.json +3 -0
  185. package/dist/runtime/server/types/shopware.d.ts +29 -0
  186. package/dist/runtime/server/types/shopware.js +0 -0
  187. package/dist/runtime/server/types/storeApiTypes.d.ts +5677 -0
  188. package/dist/runtime/shared/const/checkout.d.ts +18 -0
  189. package/dist/runtime/shared/const/checkout.js +2 -0
  190. package/dist/runtime/shared/pageTypes/checkout.pagetype.d.ts +21 -0
  191. package/dist/runtime/shared/pageTypes/checkout.pagetype.js +12 -0
  192. package/dist/types.d.mts +3 -0
  193. package/package.json +96 -0
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,10 @@
1
+ import { CartItemsLink } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { cartFragmentToken } from "../../const/passthroughTokens.js";
3
+ import { defineShopwareLink } from "../../middleware/defineShopware.js";
4
+ import { getCart } from "../../shopware-helper/getCart.js";
5
+ export default defineShopwareLink(CartItemsLink, async ({ context, passthrough }) => {
6
+ const cart = passthrough.get(cartFragmentToken) ?? await getCart(context.storefrontClient);
7
+ passthrough.set(cartFragmentToken, cart);
8
+ const targetIds = (cart.lineItems ?? []).filter((li) => li.type === "product").map((li) => li.id);
9
+ return { links: [{ sourceId: cart.token ?? "", targetIds }] };
10
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Returns the stable, same-origin checkout URL. The single-use session-handoff code
3
+ * is minted server-side when the browser navigates to this route (see
4
+ * `server/routes/checkout.ts`), so the URL itself is safe to render and cache.
5
+ *
6
+ * This mirrors the `checkoutLink` on the Cart component resolver — both resolve to
7
+ * {@link CHECKOUT_ENDPOINT_PATH}.
8
+ */
9
+ declare const _default: any;
10
+ export default _default;
@@ -0,0 +1,6 @@
1
+ import { GetCheckoutUrlAction } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { CHECKOUT_ENDPOINT_PATH } from "../../const/checkout.js";
3
+ import { defineShopwareAction } from "../../middleware/defineShopware.js";
4
+ export default defineShopwareAction(GetCheckoutUrlAction, async () => ({
5
+ checkoutUrl: CHECKOUT_ENDPOINT_PATH
6
+ }));
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import { GetCurrentCartQuery } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { cartFragmentToken } from "../../const/passthroughTokens.js";
3
+ import { defineShopwareQuery } from "../../middleware/defineShopware.js";
4
+ import { getCart } from "../../shopware-helper/getCart.js";
5
+ export default defineShopwareQuery(GetCurrentCartQuery, async ({ context, passthrough }) => {
6
+ const cart = passthrough.get(cartFragmentToken) ?? await getCart(context.storefrontClient);
7
+ passthrough.set(cartFragmentToken, cart);
8
+ return { id: cart.token ?? "" };
9
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,14 @@
1
+ import { CartRemoveItemsAction } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { defineShopwareAction } from "../../middleware/defineShopware.js";
3
+ import { handleCartMutationErrors } from "../../shopware-helper/cartErrors.js";
4
+ import { persistContextToken } from "../../shopware-helper/persistContextToken.js";
5
+ export default defineShopwareAction(CartRemoveItemsAction, async ({ event, context, input }) => {
6
+ const { storefrontClient } = context;
7
+ if (input.length === 0) return;
8
+ const cart = await storefrontClient.invoke("removeLineItem post /checkout/cart/line-item/delete", {
9
+ // The store-api types the ids as a non-empty tuple; the length guard above satisfies it.
10
+ body: { ids: input }
11
+ });
12
+ await persistContextToken(event, cart.data.token);
13
+ handleCartMutationErrors(cart.data.errors);
14
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,16 @@
1
+ import { CartUpdateItemsAction } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { defineShopwareAction } from "../../middleware/defineShopware.js";
3
+ import { handleCartMutationErrors } from "../../shopware-helper/cartErrors.js";
4
+ import { persistContextToken } from "../../shopware-helper/persistContextToken.js";
5
+ export default defineShopwareAction(CartUpdateItemsAction, async ({ event, context, input }) => {
6
+ const { storefrontClient } = context;
7
+ const items = input.filter((item) => item.quantity !== void 0).map((item) => ({ id: item.itemId, quantity: item.quantity }));
8
+ if (items.length === 0) return;
9
+ const cart = await storefrontClient.invoke("updateLineItem patch /checkout/cart/line-item", {
10
+ // Shopware only needs id + quantity to update a line item; the generated LineItem
11
+ // type over-specifies required fields, so we send the minimal shape and cast.
12
+ body: { items }
13
+ });
14
+ await persistContextToken(event, cart.data.token);
15
+ handleCartMutationErrors(cart.data.errors);
16
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,33 @@
1
+ import {
2
+ CartItemAvailability,
3
+ CartItemBase,
4
+ CartItemCost,
5
+ CartItemProductData,
6
+ CartItemQuantityRule
7
+ } from "@laioutr-core/canonical-types/entity/cart-item";
8
+ import { cartFragmentToken } from "../../const/passthroughTokens.js";
9
+ import { defineShopwareComponentResolver } from "../../middleware/defineShopware.js";
10
+ import { mapCartItem } from "../../shopware-helper/cartMapper.js";
11
+ import { getCart } from "../../shopware-helper/getCart.js";
12
+ export default defineShopwareComponentResolver({
13
+ entityType: "CartItem",
14
+ label: "Shopware Cart Item Component Resolver",
15
+ provides: [CartItemBase, CartItemCost, CartItemAvailability, CartItemQuantityRule, CartItemProductData],
16
+ resolve: async ({ entityIds, context, $entity, passthrough }) => {
17
+ const cart = passthrough.get(cartFragmentToken) ?? await getCart(context.storefrontClient);
18
+ passthrough.set(cartFragmentToken, cart);
19
+ const lineItemsById = new Map((cart.lineItems ?? []).map((li) => [li.id, li]));
20
+ const entities = entityIds.map((id) => lineItemsById.get(id)).filter((li) => !!li && li.type === "product").map((li) => {
21
+ const mapped = mapCartItem(li, context.swCurrency);
22
+ return $entity({
23
+ id: li.id,
24
+ base: () => mapped.base,
25
+ cost: () => mapped.cost,
26
+ availability: () => mapped.availability,
27
+ quantityRule: () => mapped.quantityRule,
28
+ productData: () => mapped.productData
29
+ });
30
+ });
31
+ return { entities };
32
+ }
33
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,15 @@
1
+ import { CategoryAllQuery } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { defineShopwareQuery } from "../../middleware/defineShopware.js";
3
+ export default defineShopwareQuery(CategoryAllQuery, async ({ context, pagination }) => {
4
+ const { storefrontClient } = context;
5
+ const response = await storefrontClient.invoke("readCategoryList post /category", {
6
+ body: {
7
+ page: pagination.page,
8
+ limit: pagination.limit,
9
+ includes: {
10
+ category: ["id"]
11
+ }
12
+ }
13
+ });
14
+ return { ids: (response.data.elements ?? [])?.map((element) => element.id) };
15
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,47 @@
1
+ import { CategoryBase, CategoryContent, CategoryMedia, CategorySeo } from "@laioutr-core/canonical-types/entity/category";
2
+ import { categoriesToken } from "../../const/passthroughTokens.js";
3
+ import { defineShopwareComponentResolver } from "../../middleware/defineShopware.js";
4
+ import { entitySlug } from "../../shopware-helper/mappers/slugMapper.js";
5
+ import { mapMedia } from "../../shopware-helper/mediaMapper.js";
6
+ import { swTranslated } from "../../shopware-helper/swTranslated.js";
7
+ export default defineShopwareComponentResolver({
8
+ entityType: "Category",
9
+ label: "Shopware Category Connector",
10
+ provides: [CategoryBase, CategoryContent, CategoryMedia, CategorySeo],
11
+ resolve: async ({ entityIds, context, passthrough, $entity }) => {
12
+ const { storefrontClient } = context;
13
+ const categories = passthrough.has(categoriesToken) ? passthrough.get(categoriesToken) : (await storefrontClient.invoke("readCategoryList post /category", {
14
+ // Neither association is returned by default; without `media` the media component is always empty.
15
+ body: { ids: entityIds, associations: { seoUrls: {}, media: {} } }
16
+ })).data.elements;
17
+ if (!categories) {
18
+ throw new Error(
19
+ "Categories not found in passthrough. The component resolver does not request categories from shopware at the moment."
20
+ );
21
+ }
22
+ return {
23
+ entities: categories.filter((category) => entityIds.includes(category.id)).map(
24
+ (category) => $entity({
25
+ id: category.id,
26
+ base: () => ({
27
+ slug: entitySlug(category),
28
+ title: swTranslated(category, "name") ?? category.name
29
+ }),
30
+ content: () => ({
31
+ description: { html: swTranslated(category, "description") ?? category.description ?? "" }
32
+ }),
33
+ media: () => ({
34
+ media: category.media ? [mapMedia(category.media)] : []
35
+ }),
36
+ seo: () => ({
37
+ title: category.metaTitle,
38
+ description: category.metaDescription
39
+ })
40
+ })
41
+ )
42
+ };
43
+ },
44
+ cache: {
45
+ ttl: "10 minutes"
46
+ }
47
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,12 @@
1
+ import { CategoryBySlugQuery } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { defineShopwareQuery } from "../../middleware/defineShopware.js";
3
+ import { useSeoResolver } from "../../shopware-helper/useSeoResolver.js";
4
+ export default defineShopwareQuery(CategoryBySlugQuery, async ({ context, input }) => {
5
+ const { slug } = input;
6
+ const seoResolver = useSeoResolver(context.storefrontClient);
7
+ const seoEntry = await seoResolver.resolve("category", slug);
8
+ if (!seoEntry) {
9
+ throw new Error(`No seo url found for category slug: ${slug}`);
10
+ }
11
+ return { id: seoEntry.id };
12
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,36 @@
1
+ import { ChildCategoriesLink } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { defineShopwareLink } from "../../middleware/defineShopware.js";
3
+ const readChildren = async (storefrontClient, categoryId) => {
4
+ const response = await storefrontClient.invoke("readNavigation post /navigation/{activeId}/{rootId}", {
5
+ pathParams: { activeId: categoryId, rootId: categoryId },
6
+ body: {
7
+ depth: 1,
8
+ // A projection, so unlike a filter it removes no rows and cannot disturb the server's order.
9
+ includes: { category: ["id"] }
10
+ }
11
+ });
12
+ return response.data ?? [];
13
+ };
14
+ export default defineShopwareLink({
15
+ implements: ChildCategoriesLink,
16
+ run: async ({ entityIds, context, pagination }) => {
17
+ if (entityIds.length === 0) return { links: [] };
18
+ const childrenPerSource = await Promise.all(entityIds.map((categoryId) => readChildren(context.storefrontClient, categoryId)));
19
+ return {
20
+ links: entityIds.map((sourceId, index) => {
21
+ const children = childrenPerSource[index] ?? [];
22
+ return {
23
+ sourceId,
24
+ targetIds: children.slice(pagination.offset, pagination.offset + pagination.limit).map((child) => child.id),
25
+ entityTotal: children.length
26
+ };
27
+ })
28
+ };
29
+ },
30
+ cache: {
31
+ strategy: "ttl",
32
+ ttl: "10 minutes",
33
+ // `linkRunner` already prefixes a client-env key, so locale, market and currency are covered.
34
+ buildCacheKey: ({ entityIds, pagination }) => `${[...entityIds].sort().join(",")}:${pagination.offset}:${pagination.limit}`
35
+ }
36
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,79 @@
1
+ import { paginate } from "#imports";
2
+ import { ProductListingPage } from "@laioutr-core/canonical-types/ecommerce";
3
+ import { defineShopwarePageIndex } from "../../middleware/defineShopware.js";
4
+ import { toCategoryPageEntry } from "../../shopware-helper/pageIndexEntries.js";
5
+ import { readCategoryPageMeta } from "../../shopware-helper/readCategoryPageMeta.js";
6
+ import { SHOPWARE_MAX_LIMIT, storeApiPageFetcher } from "../../shopware-helper/storeApiPageFetcher.js";
7
+ import { useSeoResolver } from "../../shopware-helper/useSeoResolver.js";
8
+ const membershipFilter = [
9
+ { type: "equals", field: "type", value: "page" },
10
+ { type: "equals", field: "active", value: true },
11
+ { type: "range", field: "level", parameters: { gt: 1 } }
12
+ ];
13
+ const pageIndexCriteria = {
14
+ associations: { seoUrls: {}, media: {} },
15
+ includes: {
16
+ category: ["id", "name", "translated", "updatedAt", "seoUrls", "media"],
17
+ seo_url: ["seoPathInfo", "isCanonical", "routeName"],
18
+ media: ["url"]
19
+ },
20
+ filter: membershipFilter,
21
+ // Page-number cursors need a stable upstream order, or a resumed walk shifts entries between passes.
22
+ sort: [{ field: "id", order: "ASC" }]
23
+ };
24
+ export default defineShopwarePageIndex({
25
+ for: ProductListingPage,
26
+ label: "Shopware Category",
27
+ batchSize: SHOPWARE_MAX_LIMIT,
28
+ cache: { ttl: "1h", search: { ttl: "5m" }, locate: { ttl: "1 day" } },
29
+ /**
30
+ * Point lookup down the same slug→id path as `bySlug.query.ts`, so the index and the rendered page
31
+ * always agree on which category a URL means. The resolver yields only an id, so the page metadata
32
+ * costs a second store-API read — absorbed by `cache.locate`, and non-fatal: the subject still
33
+ * resolves when it fails.
34
+ *
35
+ * `locales` is left unset: the store API scopes SEO reads by the `sw-language-id` header, so
36
+ * cross-locale slugs need one read per language and are not implemented. Reporting only the current
37
+ * locale would claim the category has no page in any other.
38
+ */
39
+ locate: async ({ context, params }) => {
40
+ const seoEntry = await useSeoResolver(context.storefrontClient).resolve("category", params.slug);
41
+ if (!seoEntry) return void 0;
42
+ return {
43
+ subject: { type: "Category", id: seoEntry.id },
44
+ meta: await readCategoryPageMeta(context.storefrontClient, seoEntry.id)
45
+ };
46
+ },
47
+ count: async ({ context }) => {
48
+ const response = await context.storefrontClient.invoke("readCategoryList post /category", {
49
+ body: { ...pageIndexCriteria, limit: 1, "total-count-mode": "exact" }
50
+ });
51
+ return response.data.total ?? 0;
52
+ },
53
+ /** Substring match on name and metaTitle, the same form the query-template picker uses for categories. */
54
+ search: ({ context, term, take }) => context.storefrontClient.invoke("readCategoryList post /category", {
55
+ body: {
56
+ ...pageIndexCriteria,
57
+ filter: [
58
+ ...membershipFilter,
59
+ {
60
+ type: "multi",
61
+ operator: "or",
62
+ queries: [
63
+ { type: "contains", field: "name", value: term },
64
+ { type: "contains", field: "metaTitle", value: term }
65
+ ]
66
+ }
67
+ ],
68
+ limit: take
69
+ }
70
+ }).then((response) => (response.data.elements ?? []).map(toCategoryPageEntry)),
71
+ list: ({ context, batchSize, startCursor }) => paginate(
72
+ storeApiPageFetcher(
73
+ ({ page, limit }) => context.storefrontClient.invoke("readCategoryList post /category", { body: { ...pageIndexCriteria, page, limit } }).then((response) => response.data),
74
+ toCategoryPageEntry,
75
+ batchSize
76
+ ),
77
+ startCursor
78
+ )
79
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,10 @@
1
+ import { CategoryBreadcrumbLink } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { defineShopwareLink } from "../../middleware/defineShopware.js";
3
+ export default defineShopwareLink(CategoryBreadcrumbLink, async ({ entityIds }) => ({
4
+ links: entityIds.map(
5
+ (id) => ({
6
+ sourceId: id,
7
+ targetId: id
8
+ })
9
+ )
10
+ }));
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,63 @@
1
+ import { MenuItemBase } from "@laioutr-core/canonical-types/entity/menuItem";
2
+ import { categoriesToken } from "../../const/passthroughTokens.js";
3
+ import { defineShopwareComponentResolver } from "../../middleware/defineShopware.js";
4
+ import { entitySlug } from "../../shopware-helper/mappers/slugMapper.js";
5
+ import { swTranslated } from "../../shopware-helper/swTranslated.js";
6
+ export default defineShopwareComponentResolver({
7
+ entityType: "MenuItem",
8
+ label: "Shopware Menu Connector",
9
+ provides: [MenuItemBase],
10
+ resolve: async ({ passthrough, $entity }) => {
11
+ const categories = passthrough.require(categoriesToken);
12
+ return {
13
+ entities: categories.map(
14
+ (category) => $entity({
15
+ id: category.id,
16
+ base: () => {
17
+ const baseItem = {
18
+ type: "link",
19
+ name: swTranslated(category, "name"),
20
+ childIds: category.children?.map((child) => child.id),
21
+ parentId: category.parentId
22
+ };
23
+ if (category.type === "folder") {
24
+ return {
25
+ ...baseItem,
26
+ type: "folder"
27
+ };
28
+ }
29
+ if (category.type === "link") {
30
+ return {
31
+ ...baseItem,
32
+ link: {
33
+ type: "url",
34
+ href: category.internalLink ?? category.externalLink ?? "/"
35
+ }
36
+ };
37
+ }
38
+ if (category.type === "page") {
39
+ return {
40
+ ...baseItem,
41
+ link: {
42
+ type: "reference",
43
+ reference: {
44
+ type: category.linkType ?? "Category",
45
+ slug: entitySlug(category),
46
+ id: category.id
47
+ }
48
+ }
49
+ };
50
+ }
51
+ return {
52
+ ...baseItem,
53
+ type: "folder"
54
+ };
55
+ }
56
+ })
57
+ )
58
+ };
59
+ },
60
+ cache: {
61
+ ttl: "10 minutes"
62
+ }
63
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,29 @@
1
+ import { MenuByAliasQuery } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { categoriesToken } from "../../const/passthroughTokens.js";
3
+ import { defineShopwareQuery } from "../../middleware/defineShopware.js";
4
+ import { flattenCategories } from "../../shopware-helper/categoryFlattener.js";
5
+ export default defineShopwareQuery({
6
+ implements: MenuByAliasQuery,
7
+ run: async ({ input, context, passthrough }) => {
8
+ const { alias } = input;
9
+ const response = await context.storefrontClient.invoke("readNavigation post /navigation/{activeId}/{rootId}", {
10
+ pathParams: {
11
+ activeId: alias,
12
+ rootId: alias
13
+ },
14
+ body: { associations: { seoUrls: {} } }
15
+ });
16
+ const flattenedCategories = flattenCategories(response.data ?? []);
17
+ passthrough.set(categoriesToken, flattenedCategories);
18
+ return {
19
+ ids: flattenedCategories.map((item) => item.id)
20
+ };
21
+ },
22
+ cache: {
23
+ strategy: "ttl",
24
+ ttl: "10 minutes",
25
+ buildCacheKey({ input }) {
26
+ return input.alias;
27
+ }
28
+ }
29
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,25 @@
1
+ import { MenuByAliasQuery } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { defineShopwareQueryTemplateProvider } from "../../middleware/defineShopware.js";
3
+ export default defineShopwareQueryTemplateProvider({
4
+ for: MenuByAliasQuery,
5
+ run: async ({ input, context }) => [
6
+ {
7
+ input: {
8
+ alias: "main-navigation"
9
+ },
10
+ label: "Main Navigation"
11
+ },
12
+ {
13
+ input: {
14
+ alias: "service-navigation"
15
+ },
16
+ label: "Service Navigation"
17
+ },
18
+ {
19
+ input: {
20
+ alias: "footer-navigation"
21
+ },
22
+ label: "Footer Navigation"
23
+ }
24
+ ]
25
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,27 @@
1
+ import { consola } from "consola";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { SubscribeAction } from "@laioutr-core/canonical-types/newsletter";
4
+ import { defineShopwareAction } from "../../middleware/defineShopware.js";
5
+ const logger = consola.withTag("shopware/newsletter");
6
+ export default defineShopwareAction(SubscribeAction, async ({ context, input }) => {
7
+ const { storefrontUrl } = useRuntimeConfig()["@laioutr/app-shopware"];
8
+ if (!storefrontUrl) {
9
+ logger.error("Cannot subscribe to newsletter: storefrontUrl is not configured");
10
+ return { status: "error" };
11
+ }
12
+ await context.storefrontClient.invoke("subscribeToNewsletter post /newsletter/subscribe", {
13
+ body: {
14
+ option: "subscribe",
15
+ storefrontUrl,
16
+ email: input.email,
17
+ firstName: input.person?.firstName,
18
+ lastName: input.person?.lastName,
19
+ street: input.address?.address1,
20
+ city: input.address?.city,
21
+ zipCode: input.address?.postalCode,
22
+ languageId: context.currentSystemEntities.locale.languageId,
23
+ salutationId: context.systemEntities.salutations.find((salutation) => salutation.key === input.person?.salutation)?.id
24
+ }
25
+ });
26
+ return { status: "success" };
27
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,119 @@
1
+ import { Money } from "@screeny05/ts-money";
2
+ import {
3
+ ProductBase,
4
+ ProductDefaultVariant,
5
+ ProductDescription,
6
+ ProductFlags,
7
+ ProductInfo,
8
+ ProductMedia,
9
+ ProductPrices,
10
+ ProductRating,
11
+ ProductSeo
12
+ } from "@laioutr-core/canonical-types/entity/product";
13
+ import { FALLBACK_IMAGE } from "../../const/fallbacks.js";
14
+ import { parentIdToDefaultVariantIdToken, productVariantsToken } from "../../const/passthroughTokens.js";
15
+ import { defineShopwareComponentResolver } from "../../middleware/defineShopware.js";
16
+ import { fetchAllProducts } from "../../shopware-helper/fetchAllProductVariants.js";
17
+ import { entitySlug } from "../../shopware-helper/mappers/slugMapper.js";
18
+ import { mapMedia } from "../../shopware-helper/mediaMapper.js";
19
+ import { swTranslated } from "../../shopware-helper/swTranslated.js";
20
+ export default defineShopwareComponentResolver({
21
+ label: "Shopware Product Connector",
22
+ entityType: "Product",
23
+ provides: [
24
+ ProductBase,
25
+ ProductInfo,
26
+ ProductPrices,
27
+ ProductMedia,
28
+ ProductFlags,
29
+ ProductSeo,
30
+ ProductDescription,
31
+ ProductDefaultVariant,
32
+ ProductRating
33
+ ],
34
+ resolve: async ({ entityIds, context, $entity, passthrough }) => {
35
+ const parentIdToDefaultVariantId = passthrough.get(parentIdToDefaultVariantIdToken);
36
+ const variantIds = entityIds.map((id) => parentIdToDefaultVariantId?.[id] ?? id);
37
+ const loadedVariants = passthrough.get(productVariantsToken) ?? [];
38
+ const missingVariantIds = variantIds.filter((id) => !loadedVariants.some((variant) => variant.id === id));
39
+ if (missingVariantIds.length > 0) {
40
+ const response = await fetchAllProducts(context.storefrontClient, { productIds: missingVariantIds, loadVariants: false });
41
+ loadedVariants.push(...response);
42
+ }
43
+ const shopwareProducts = variantIds.map((id) => {
44
+ const rawVariant = loadedVariants.find((variant) => variant.id === id);
45
+ if (!rawVariant) return void 0;
46
+ return {
47
+ rawVariant,
48
+ rawProduct: loadedVariants.find((variant) => variant.id === variant.parentId) ?? rawVariant
49
+ };
50
+ }).filter((product) => !!product);
51
+ const entities = shopwareProducts.map(({ rawProduct, rawVariant }) => {
52
+ const mappedCover = rawProduct.cover?.media ? mapMedia(rawVariant.cover?.media ?? rawProduct.cover.media) : FALLBACK_IMAGE;
53
+ return $entity({
54
+ // The parent-id is the actual product-id, even if the product has variants.
55
+ id: rawProduct.id,
56
+ base: {
57
+ name: swTranslated(rawProduct, "name"),
58
+ slug: entitySlug(rawProduct)
59
+ },
60
+ defaultVariant: {
61
+ id: rawVariant.id
62
+ },
63
+ info: {
64
+ cover: mappedCover,
65
+ shortDescription: swTranslated(rawProduct, "description") || swTranslated(rawVariant, "description"),
66
+ brand: swTranslated(rawProduct.manufacturer, "name") || swTranslated(rawVariant.manufacturer, "name") || ""
67
+ },
68
+ description: {
69
+ html: swTranslated(rawProduct, "description") || swTranslated(rawVariant, "description") || ""
70
+ },
71
+ seo: {
72
+ title: swTranslated(rawProduct, "metaTitle") || swTranslated(rawVariant, "metaTitle"),
73
+ description: swTranslated(rawProduct, "metaDescription") || swTranslated(rawVariant, "metaDescription")
74
+ },
75
+ media: () => {
76
+ const mappedMedia = (rawProduct.media ?? rawVariant.media)?.filter((image) => !!image.media).map((image) => mapMedia(image.media)) ?? [];
77
+ const isFirstMediaCover = mappedCover.sources.at(0)?.src === mappedMedia.at(0)?.sources.at(0)?.src;
78
+ const allMedia = isFirstMediaCover ? mappedMedia : [mappedCover, ...mappedMedia];
79
+ return {
80
+ cover: mappedCover,
81
+ media: allMedia,
82
+ images: allMedia.filter((media) => media.type === "image")
83
+ };
84
+ },
85
+ prices: () => {
86
+ const rawListPrice = rawProduct.calculatedCheapestPrice?.listPrice?.price ?? rawProduct.calculatedPrice.listPrice?.price ?? rawVariant.calculatedCheapestPrice?.listPrice?.price ?? rawVariant.calculatedPrice.listPrice?.price;
87
+ const listPrice = rawListPrice ? Money.fromDecimal(rawListPrice, context.swCurrency) : void 0;
88
+ const totalPrice = Money.fromDecimal(
89
+ rawProduct.calculatedCheapestPrice?.totalPrice ?? rawProduct.calculatedPrice.totalPrice,
90
+ context.swCurrency
91
+ );
92
+ const hasSavings = typeof listPrice === "object" && listPrice.greaterThan(totalPrice);
93
+ const savingsPercent = hasSavings ? 100 - totalPrice.percentageOf(listPrice) : void 0;
94
+ return {
95
+ price: totalPrice,
96
+ strikethroughPrice: hasSavings ? listPrice : void 0,
97
+ savingsPercent,
98
+ isOnSale: hasSavings,
99
+ isStartingFrom: rawProduct.calculatedCheapestPrice?.hasRange || rawProduct.calculatedPrice.hasRange
100
+ };
101
+ },
102
+ flags: [],
103
+ rating: {
104
+ average: rawProduct.ratingAverage ?? rawVariant.ratingAverage ?? 0,
105
+ count: rawProduct.productReviews?.length ?? 0
106
+ }
107
+ });
108
+ });
109
+ return { entities };
110
+ },
111
+ cache: {
112
+ ttl: "1 day",
113
+ components: {
114
+ prices: {
115
+ ttl: "15 minutes"
116
+ }
117
+ }
118
+ }
119
+ });