@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,54 @@
1
+ import { ProductSearchPage } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { SuggestedSearchSearchQuery } from "@laioutr-core/canonical-types/suggested-search";
3
+ import { suggestionResultsFragmentToken } from "../../const/passthroughTokens.js";
4
+ import { defineShopwareQuery } from "../../middleware/defineShopware.js";
5
+ import { createFallbackSlug, entitySlug } from "../../shopware-helper/mappers/slugMapper.js";
6
+ export default defineShopwareQuery(SuggestedSearchSearchQuery, async ({ context, input, passthrough }) => {
7
+ const { storefrontClient } = context;
8
+ const { query } = input;
9
+ const { data } = await storefrontClient.invoke("searchSuggest post /search-suggest", {
10
+ body: { search: query ?? "" }
11
+ });
12
+ const res = data;
13
+ const querySuggestionsResults = Object.values(res.extensions?.completionResult ?? {}).filter((val) => val !== "array_struct").map((val, index) => ({
14
+ id: `query_suggestion_${index}`,
15
+ type: "query-suggestion",
16
+ title: val,
17
+ link: { type: "pageType", pageType: ProductSearchPage, params: { q: val } }
18
+ }));
19
+ const brandResults = Object.values(res.extensions?.multiSuggestResult?.suggestResults?.product_manufacturer?.elements ?? []).map(
20
+ (manufacturer) => ({
21
+ id: manufacturer.id,
22
+ type: "brand",
23
+ title: manufacturer.name,
24
+ link: {
25
+ type: "reference",
26
+ reference: { type: "brand", id: manufacturer.id, slug: createFallbackSlug(manufacturer.name, manufacturer.id) }
27
+ }
28
+ })
29
+ );
30
+ const categoryResults = Object.values(res.extensions?.multiSuggestResult?.suggestResults?.category?.elements ?? []).map((category) => ({
31
+ id: category.id,
32
+ type: "category",
33
+ title: category.name,
34
+ link: {
35
+ type: "reference",
36
+ reference: { type: "category", id: category.id, slug: entitySlug(category) }
37
+ }
38
+ }));
39
+ const productResults = (data.elements ?? []).map((product) => ({
40
+ id: product.id,
41
+ type: "product",
42
+ title: product.name,
43
+ link: {
44
+ type: "reference",
45
+ reference: { type: "product", id: product.id, slug: entitySlug(product) }
46
+ }
47
+ }));
48
+ const id = `search-suggest:${query}`;
49
+ passthrough.set(suggestionResultsFragmentToken, {
50
+ id,
51
+ suggestions: [...querySuggestionsResults, ...brandResults, ...categoryResults, ...productResults]
52
+ });
53
+ return { id };
54
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,22 @@
1
+ import { SuggestedSearchEntryBase } from "@laioutr-core/canonical-types/entity/suggested-search-entry";
2
+ import { suggestionResultsFragmentToken } from "../../const/passthroughTokens.js";
3
+ import { defineShopwareComponentResolver } from "../../middleware/defineShopware.js";
4
+ export default defineShopwareComponentResolver({
5
+ label: "Shopware Suggested Search Entry Resolver",
6
+ entityType: "SuggestedSearchEntry",
7
+ provides: [SuggestedSearchEntryBase],
8
+ resolve: ({ passthrough, $entity }) => {
9
+ const results = passthrough.require(suggestionResultsFragmentToken);
10
+ const entities = results.suggestions.map(
11
+ ({ id, type, title, link }) => $entity({
12
+ id,
13
+ base: () => ({
14
+ type,
15
+ title,
16
+ link
17
+ })
18
+ })
19
+ );
20
+ return { entities };
21
+ }
22
+ });
@@ -0,0 +1,2 @@
1
+ import { LinkSingle } from '@laioutr-core/orchestr/types';
2
+ export declare const isLinkSingle: (link: unknown) => link is LinkSingle;
@@ -0,0 +1 @@
1
+ export const isLinkSingle = (link) => typeof link === "object" && link !== null && typeof link.sourceId === "string" && typeof link.targetId === "string";
@@ -0,0 +1 @@
1
+ export declare const matchAndMap: <T, U>(entityIds: string[], sourceData: T[] | undefined, matchEntityCb: (id: string, entry: T) => boolean, mapCb: (entity: T) => U) => any;
@@ -0,0 +1,15 @@
1
+ const isEntry = (obj) => typeof obj !== "undefined";
2
+ export const matchAndMap = (entityIds, sourceData, matchEntityCb, mapCb) => {
3
+ if (!sourceData || sourceData.length === 0) {
4
+ return {};
5
+ }
6
+ return Object.fromEntries(
7
+ entityIds.map((id) => {
8
+ const rawEntity = sourceData.find((entry) => matchEntityCb(id, entry));
9
+ if (!rawEntity) {
10
+ return void 0;
11
+ }
12
+ return [id, mapCb(rawEntity)];
13
+ }).filter(isEntry)
14
+ );
15
+ };
@@ -0,0 +1,18 @@
1
+ export declare const resolveProductFields: ({ loadVariants }: {
2
+ loadVariants: boolean;
3
+ }) => {
4
+ associations: {
5
+ [key: string]: /*elided*/ any;
6
+ };
7
+ includes: {
8
+ [key: string]: string[];
9
+ };
10
+ };
11
+ export declare const resolveProductVariantFields: () => {
12
+ associations: {
13
+ [key: string]: /*elided*/ any;
14
+ };
15
+ includes: {
16
+ [key: string]: string[];
17
+ };
18
+ };
@@ -0,0 +1,145 @@
1
+ import { ProductVariantsLink } from "@laioutr-core/canonical-types/ecommerce";
2
+ import { MediaIncludes } from "../const/includes.js";
3
+ const unique = (arr) => Array.from(new Set(arr));
4
+ const addAssociation = (name, add, association = {}) => add ? { [name]: association } : {};
5
+ const _resolveRequestedFields = ({
6
+ requestedComponents,
7
+ requestedLinks
8
+ }) => {
9
+ const requestedVariants = requestedLinks[ProductVariantsLink];
10
+ const variantFields = requestedVariants ? resolveProductVariantFields() : void 0;
11
+ const associations = {
12
+ ...requestedComponents.includes("media") ? { cover: { associations: { media: {} } }, media: { associations: { media: {} } } } : {},
13
+ // gallery images (via product_media -> media)
14
+ ...requestedComponents.includes("options") ? { options: { associations: { group: {} } } } : {},
15
+ // variant options like Color/Size + their group names
16
+ ...requestedVariants ? {
17
+ children: {
18
+ associations: variantFields?.associations ?? {}
19
+ }
20
+ } : {}
21
+ };
22
+ const includes = {
23
+ product: unique([
24
+ "id",
25
+ "parentId",
26
+ ...requestedComponents.includes("base") ? ["name", "seoUrls"] : [],
27
+ ...requestedComponents.includes("info") ? ["name", "productNumber", "ean", "translated", "manufacturer", "description", "cover"] : [],
28
+ ...requestedComponents.includes("description") ? ["translated", "description"] : [],
29
+ ...requestedComponents.includes("seo") ? ["metaTitle", "metaDescription", "seoUrls"] : [],
30
+ ...requestedComponents.includes("media") ? ["cover", "media"] : [],
31
+ ...requestedComponents.includes("prices") ? ["minPurchase", "purchaseSteps", "maxPurchase", "calculatedPrice", "calculatedPrices"] : [],
32
+ ...requestedLinks["ecommerce/product/variants"] ? ["children"] : [],
33
+ ...variantFields?.includes?.product ?? []
34
+ ]),
35
+ product_media: requestedComponents.includes("media") ? ["id", "mediaId", "media"] : [],
36
+ media: requestedComponents.includes("media") ? MediaIncludes : [],
37
+ property_group_option: requestedComponents.includes("availability") ? ["id", "name", "group"] : [],
38
+ property_group: requestedComponents.includes("availability") ? ["id", "name"] : []
39
+ };
40
+ return { associations, includes };
41
+ };
42
+ const mergeIncludes = (includes1, includes2) => {
43
+ const merged = {};
44
+ for (const [key, value] of Object.entries(includes1)) {
45
+ merged[key] = [...value];
46
+ }
47
+ for (const [key, value] of Object.entries(includes2)) {
48
+ merged[key] = [...merged[key] ?? [], ...value];
49
+ }
50
+ for (const [key, value] of Object.entries(merged)) {
51
+ merged[key] = unique([...value]);
52
+ }
53
+ return merged;
54
+ };
55
+ export const resolveProductFields = ({ loadVariants }) => {
56
+ const variantFields = loadVariants ? resolveProductVariantFields() : void 0;
57
+ const associations = {
58
+ cover: { associations: { media: {} } },
59
+ media: { associations: { media: {} } },
60
+ ...addAssociation("children", loadVariants, variantFields?.associations ? { associations: variantFields?.associations } : {})
61
+ };
62
+ const includes = mergeIncludes(
63
+ {
64
+ product: [
65
+ "id",
66
+ "parentId",
67
+ "name",
68
+ "seoUrls",
69
+ "productNumber",
70
+ "ean",
71
+ "translated",
72
+ "manufacturer",
73
+ "description",
74
+ "cover",
75
+ "metaTitle",
76
+ "metaDescription",
77
+ "cover",
78
+ "media",
79
+ "minPurchase",
80
+ "purchaseSteps",
81
+ "maxPurchase",
82
+ "calculatedPrice",
83
+ "calculatedPrices",
84
+ "children",
85
+ "ratingAverage",
86
+ "productReviews"
87
+ ],
88
+ product_media: ["id", "mediaId", "media"],
89
+ media: MediaIncludes
90
+ },
91
+ variantFields?.includes ?? {}
92
+ );
93
+ return {
94
+ associations,
95
+ includes
96
+ };
97
+ };
98
+ export const resolveProductVariantFields = () => {
99
+ const associations = {
100
+ cover: { associations: { media: {} } },
101
+ // main image
102
+ media: { associations: { media: {} } },
103
+ // gallery images (via product_media -> media)
104
+ options: { associations: { group: {} } },
105
+ // variant options like Color/Size + their group names
106
+ manufacturer: {},
107
+ deliveryTime: {},
108
+ prices: { associations: { rule: {} } }
109
+ };
110
+ const includes = {
111
+ product: [
112
+ "id",
113
+ "parentId",
114
+ "name",
115
+ "productNumber",
116
+ "ean",
117
+ "available",
118
+ "availableStock",
119
+ "stock",
120
+ "minPurchase",
121
+ "purchaseSteps",
122
+ "maxPurchase",
123
+ "calculatedPrice",
124
+ "calculatedPrices",
125
+ "cover",
126
+ "media",
127
+ "options",
128
+ "optionIds",
129
+ "translated",
130
+ "manufacturer",
131
+ "deliveryTime",
132
+ "prices",
133
+ "ratingAverage",
134
+ "productReviews"
135
+ ],
136
+ product_media: ["id", "mediaId", "media"],
137
+ media: MediaIncludes,
138
+ property_group_option: ["id", "name", "group", "translated"],
139
+ property_group: ["id", "name", "translated"]
140
+ };
141
+ return {
142
+ associations,
143
+ includes
144
+ };
145
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Same-origin endpoint the embedded checkout section calls on `laioutr:auth-changed`.
3
+ * Redeems the single-use code for the customer-bound context token (login) or clears the
4
+ * token (logout). The token is fetched server-to-server and stored in an httpOnly cookie;
5
+ * it never reaches the browser.
6
+ */
7
+ declare const _default: any;
8
+ export default _default;
@@ -0,0 +1,16 @@
1
+ import { defineEventHandler, readBody, useRuntimeConfig } from "#imports";
2
+ import { adoptSessionToken } from "../shopware-helper/adoptSession.js";
3
+ import { clearContextToken, persistContextToken } from "../shopware-helper/persistContextToken.js";
4
+ import { adoptSession } from "../shopware-helper/resolveAdoptSession.js";
5
+ export default defineEventHandler(async (event) => {
6
+ const config = useRuntimeConfig()["@laioutr/app-shopware"];
7
+ const body = await readBody(event);
8
+ const code = typeof body?.code === "string" ? body.code : null;
9
+ return adoptSession({
10
+ config: { endpoint: config.endpoint, accessToken: config.accessToken },
11
+ code,
12
+ adopt: adoptSessionToken,
13
+ persist: (token) => persistContextToken(event, token),
14
+ clear: () => clearContextToken(event)
15
+ });
16
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Same-origin checkout handoff route (registered at {@link CHECKOUT_ENDPOINT_PATH}).
3
+ *
4
+ * A cart checkout button is a plain anchor to this route. On navigation the browser
5
+ * sends the `sw-context-token` cookie; {@link resolveCheckout} mints a single-use
6
+ * handoff code server-to-server and this thin adapter 302s to the storefront's
7
+ * `connect-session`, which adopts the cart context into the storefront session. The
8
+ * fail-closed decision matrix lives in `resolveCheckout` (unit-tested); no path mutates the cart.
9
+ *
10
+ * A first-time visitor has no `sw-context-token` cookie yet (the cookie is only set by a cart
11
+ * action or login). Without one, `resolveCheckout` would bounce to the laioutr root and the
12
+ * embedded storefront would never load. So when the cookie is absent we bootstrap a guest
13
+ * context token and persist it, giving the storefront a session to adopt (an empty guest cart).
14
+ */
15
+ declare const _default: any;
16
+ export default _default;
@@ -0,0 +1,31 @@
1
+ import { consola } from "consola";
2
+ import { createError, defineEventHandler, getCookie, getRequestURL, sendRedirect, useRuntimeConfig } from "#imports";
3
+ import { CONTEXT_TOKEN_COOKIE } from "../const/cookieKeys.js";
4
+ import { bootstrapGuestContextToken } from "../shopware-helper/bootstrapContextToken.js";
5
+ import { persistContextToken } from "../shopware-helper/persistContextToken.js";
6
+ import { resolveCheckout } from "../shopware-helper/resolveCheckout.js";
7
+ import { mintSessionHandoffCode } from "../shopware-helper/sessionHandoff.js";
8
+ const log = consola.withTag("shopware/checkout");
9
+ export default defineEventHandler(async (event) => {
10
+ const config = useRuntimeConfig()["@laioutr/app-shopware"];
11
+ let contextToken = getCookie(event, CONTEXT_TOKEN_COOKIE);
12
+ if (!contextToken) {
13
+ try {
14
+ contextToken = await bootstrapGuestContextToken({ endpoint: config.endpoint, accessToken: config.accessToken });
15
+ await persistContextToken(event, contextToken);
16
+ } catch (cause) {
17
+ log.error("Failed to bootstrap a guest context token", cause);
18
+ }
19
+ }
20
+ const plan = await resolveCheckout({
21
+ config,
22
+ contextToken,
23
+ origin: getRequestURL(event).origin,
24
+ mint: mintSessionHandoffCode
25
+ });
26
+ if (plan.kind === "error") {
27
+ log.error(plan.statusMessage, plan.cause);
28
+ throw createError({ statusCode: plan.statusCode, statusMessage: plan.statusMessage });
29
+ }
30
+ return sendRedirect(event, plan.url, 302);
31
+ });
@@ -0,0 +1,18 @@
1
+ export interface AdoptSessionTokenParams {
2
+ /** Store-API base URL, e.g. `https://shop.example.com/store-api`. */
3
+ endpoint: string;
4
+ /** Sales-channel access key (sent as the `sw-access-key` header). */
5
+ accessToken: string;
6
+ /** Single-use handoff code received in a `laioutr:auth-changed` message. */
7
+ code: string;
8
+ }
9
+ /**
10
+ * Redeem a single-use handoff code for its customer-bound `sw-context-token` against the
11
+ * `LaioutrConnector` plugin's `POST /store-api/laioutr/session-adopt` endpoint.
12
+ *
13
+ * Called server-to-server after a `laioutr:auth-changed` message: the token is returned to
14
+ * laioutr's server only and never reaches the browser. The code is single-use and ~60s TTL.
15
+ *
16
+ * @throws with the plugin's rejection reason on a non-2xx response, or when no token is returned.
17
+ */
18
+ export declare const adoptSessionToken: (params: AdoptSessionTokenParams) => Promise<string>;
@@ -0,0 +1,23 @@
1
+ const describeAdoptError = (err) => {
2
+ const e = err;
3
+ const apiError = e?.data?.errors?.[0];
4
+ return apiError?.detail ?? apiError?.title ?? e?.data?.message ?? e?.message ?? "unknown error";
5
+ };
6
+ export const adoptSessionToken = async (params) => {
7
+ const base = params.endpoint.replace(/\/+$/, "");
8
+ let res;
9
+ try {
10
+ res = await $fetch(`${base}/laioutr/session-adopt`, {
11
+ method: "POST",
12
+ headers: { "sw-access-key": params.accessToken },
13
+ body: { code: params.code }
14
+ });
15
+ } catch (err) {
16
+ throw new Error(`session-adopt rejected: ${describeAdoptError(err)}`);
17
+ }
18
+ const token = res?.["context-token"];
19
+ if (!token) {
20
+ throw new Error("session-adopt returned no context token");
21
+ }
22
+ return token;
23
+ };
@@ -0,0 +1,18 @@
1
+ export interface BootstrapContextTokenParams {
2
+ /** Store-API base URL, e.g. `https://shop.example.com/store-api`. */
3
+ endpoint: string;
4
+ /** Sales-channel access key (sent as the `sw-access-key` header). */
5
+ accessToken: string;
6
+ }
7
+ /**
8
+ * Mint a fresh guest `sw-context-token` by calling the store-api `GET /context` with only the
9
+ * sales-channel access key. Shopware creates a new guest context and returns its token in the
10
+ * `sw-context-token` response header (hence `$fetch.raw`, to read the header). Read-only — it
11
+ * does not touch the cart.
12
+ *
13
+ * Used by the checkout handoff route so a first-time visitor (no context cookie yet) still gets
14
+ * a session the embedded storefront can adopt, instead of being bounced to the laioutr root.
15
+ *
16
+ * @throws when the store-api call fails or returns no context token.
17
+ */
18
+ export declare const bootstrapGuestContextToken: (params: BootstrapContextTokenParams) => Promise<string>;
@@ -0,0 +1,22 @@
1
+ const describeBootstrapError = (err) => {
2
+ const e = err;
3
+ const apiError = e?.data?.errors?.[0];
4
+ return apiError?.detail ?? apiError?.title ?? e?.data?.message ?? e?.message ?? "unknown error";
5
+ };
6
+ export const bootstrapGuestContextToken = async (params) => {
7
+ const base = params.endpoint.replace(/\/+$/, "");
8
+ let token;
9
+ try {
10
+ const res = await $fetch.raw(`${base}/context`, {
11
+ method: "GET",
12
+ headers: { "sw-access-key": params.accessToken }
13
+ });
14
+ token = res.headers.get("sw-context-token");
15
+ } catch (err) {
16
+ throw new Error(`guest context bootstrap failed: ${describeBootstrapError(err)}`);
17
+ }
18
+ if (!token) {
19
+ throw new Error("guest context bootstrap returned no context token");
20
+ }
21
+ return token;
22
+ };
@@ -0,0 +1,11 @@
1
+ import { Schemas } from '../types/storeApiTypes.js';
2
+ /**
3
+ * Inspect the `errors` of a Shopware cart mutation response.
4
+ *
5
+ * Blocking errors (`level: 20`) throw a generic error with the Shopware message.
6
+ * Notices (`level: 0`) and warnings (`level: 10`) are logged, not dropped, so cart-side
7
+ * issues stay diagnosable without failing the mutation. (Mapping to canonical
8
+ * Product* errors is deferred — Shopware's CartError shape carries no reliable
9
+ * variant reference.)
10
+ */
11
+ export declare const handleCartMutationErrors: (errors: Schemas["Cart"]["errors"]) => void;
@@ -0,0 +1,21 @@
1
+ import { consola } from "consola";
2
+ const logger = consola.withTag("shopware/cart");
3
+ const normalizeCartErrors = (errors) => {
4
+ if (!errors) return [];
5
+ if (Array.isArray(errors)) return errors;
6
+ return Object.values(errors);
7
+ };
8
+ export const handleCartMutationErrors = (errors) => {
9
+ for (const error of normalizeCartErrors(errors)) {
10
+ const level = error.level ?? 0;
11
+ const detail = { key: error.key, messageKey: error.messageKey, message: error.message };
12
+ if (level >= 20) {
13
+ throw new Error(`Shopware cart error: ${error.messageKey ?? error.key ?? "unknown"} \u2014 ${error.message ?? ""}`.trim());
14
+ }
15
+ if (level >= 10) {
16
+ logger.warn("Cart warning", detail);
17
+ } else {
18
+ logger.info("Cart notice", detail);
19
+ }
20
+ }
21
+ };
@@ -0,0 +1,14 @@
1
+ import { CartCost } from '@laioutr-core/canonical-types/entity/cart';
2
+ import { CartItemAvailability, CartItemBase, CartItemCost, CartItemProductData, CartItemQuantityRule } from '@laioutr-core/canonical-types/entity/cart-item';
3
+ import { EntityComponentType } from '@laioutr-core/core-types/orchestr';
4
+ import { Schemas } from '../types/storeApiTypes.js';
5
+ /** Map a Shopware cart's aggregate price into the canonical nested `CartCost`. */
6
+ export declare const mapCartCost: (cart: Schemas["Cart"], currency: string) => EntityComponentType<typeof CartCost>;
7
+ /** Map a Shopware product line item into the canonical `CartItem` component values. */
8
+ export declare const mapCartItem: (lineItem: Schemas["LineItem"], currency: string) => {
9
+ base: EntityComponentType<typeof CartItemBase>;
10
+ cost: EntityComponentType<typeof CartItemCost>;
11
+ availability: EntityComponentType<typeof CartItemAvailability>;
12
+ quantityRule: EntityComponentType<typeof CartItemQuantityRule>;
13
+ productData: EntityComponentType<typeof CartItemProductData>;
14
+ };
@@ -0,0 +1,83 @@
1
+ import { Money } from "@screeny05/ts-money";
2
+ import { createFallbackSlug, getEntitySeoSlug } from "./mappers/slugMapper.js";
3
+ import { mapMedia } from "./mediaMapper.js";
4
+ export const mapCartCost = (cart, currency) => {
5
+ const money = (value) => Money.fromDecimal(value, currency);
6
+ const lineItems = cart.lineItems ?? [];
7
+ const deliveries = cart.deliveries ?? [];
8
+ const price = cart.price;
9
+ const subtotalValue = price?.positionPrice ?? lineItems.reduce((sum, li) => sum + (li.price?.totalPrice ?? 0), 0);
10
+ const totalValue = price?.totalPrice ?? subtotalValue;
11
+ const shippingValue = deliveries.reduce((sum, d) => sum + (d.shippingCosts?.totalPrice ?? 0), 0);
12
+ const itemTaxValue = (price?.calculatedTaxes ?? []).reduce((sum, t) => sum + (t.tax ?? 0), 0);
13
+ const shippingTaxValue = deliveries.reduce(
14
+ (sum, d) => sum + (d.shippingCosts?.calculatedTaxes ?? []).reduce((s, t) => s + (t.tax ?? 0), 0),
15
+ 0
16
+ );
17
+ const taxIncluded = price?.taxStatus === "gross";
18
+ const taxes = (price?.calculatedTaxes ?? []).map((t) => ({
19
+ rate: (t.taxRate ?? 0) / 100,
20
+ amount: money(t.tax ?? 0)
21
+ }));
22
+ return {
23
+ subtotal: money(subtotalValue),
24
+ subtotalIsEstimated: false,
25
+ total: money(totalValue),
26
+ totalIsEstimated: false,
27
+ ...deliveries.length > 0 ? { shipping: { total: money(shippingValue), isEstimated: false } } : {},
28
+ tax: { total: money(itemTaxValue + shippingTaxValue), isEstimated: false, isIncluded: taxIncluded },
29
+ ...taxes.length > 0 ? { taxes } : {}
30
+ };
31
+ };
32
+ export const mapCartItem = (lineItem, currency) => {
33
+ const money = (value) => Money.fromDecimal(value, currency);
34
+ const price = lineItem.price;
35
+ const qtyInfo = lineItem.quantityInformation;
36
+ const payload = lineItem.payload ?? {};
37
+ const unit = price?.unitPrice ?? 0;
38
+ const total = price?.totalPrice ?? 0;
39
+ const listPrice = price?.listPrice?.price;
40
+ const parentId = payload.parentId ?? lineItem.referencedId ?? lineItem.id;
41
+ const slug = getEntitySeoSlug(payload) ?? createFallbackSlug(lineItem.label ?? "", lineItem.referencedId ?? lineItem.id);
42
+ const availableStock = payload.availableStock;
43
+ const inStock = availableStock === void 0 ? payload.available !== false : availableStock > 0;
44
+ return {
45
+ base: {
46
+ type: "product",
47
+ quantity: lineItem.quantity,
48
+ title: lineItem.label ?? "",
49
+ code: payload.productNumber,
50
+ // Cart covers are product images (never video), so the narrowing cast is safe.
51
+ cover: lineItem.cover?.media ? mapMedia(lineItem.cover.media) : void 0,
52
+ link: {
53
+ type: "reference",
54
+ reference: { type: "Product", id: parentId, slug }
55
+ }
56
+ },
57
+ cost: {
58
+ single: money(unit),
59
+ singleStrikethrough: listPrice && listPrice > unit ? money(listPrice) : void 0,
60
+ subtotal: money(total),
61
+ total: money(total)
62
+ },
63
+ availability: {
64
+ // `quantity` is Shopware's own purchasable quantity: `quantityInformation.maxPurchase`
65
+ // is the product's `calculatedMaxPurchase` — stock-clamped (`min(cap, availableStock)`)
66
+ // for closeout products, and the configured cap for backorder products. This is the
67
+ // number Shopware's storefront quantity selector uses. Physical stock is not surfaced
68
+ // here on purpose (Shopware doesn't put `availableStock` on the cart line's
69
+ // `quantityInformation`); expose true stock via a product association if a low-stock UI
70
+ // ever needs it. The purchase cap is also carried in `quantityRule.max` below.
71
+ quantity: qtyInfo?.maxPurchase ?? availableStock ?? lineItem.quantity,
72
+ status: inStock ? "inStock" : "outOfStock"
73
+ },
74
+ quantityRule: {
75
+ min: qtyInfo?.minPurchase ?? 1,
76
+ max: qtyInfo?.maxPurchase,
77
+ increment: qtyInfo?.purchaseSteps ?? 1,
78
+ canChange: true
79
+ },
80
+ // Shopware referencePrice → canonical UnitPrice mapping is deferred; productData is optional.
81
+ productData: void 0
82
+ };
83
+ };
@@ -0,0 +1,75 @@
1
+ import type { ShopwareCategory } from '../types/shopware.js';
2
+ /**
3
+ * Returns a flat list of shopware categories
4
+ *
5
+ * Order is left as the navigation route returned it: that route nests and sorts each level
6
+ * server-side, so re-sorting here would only risk diverging from the shop's own order.
7
+ *
8
+ * **Warning**: This function mutates the input categories by overwriting their parentId property.
9
+ */
10
+ export declare const flattenCategories: (categories: ShopwareCategory[], parent?: ShopwareCategory) => {
11
+ active?: boolean;
12
+ afterCategoryId?: string;
13
+ afterCategoryVersionId?: string;
14
+ apiAlias: "category";
15
+ breadcrumb: string[];
16
+ childCount: number;
17
+ children: import("../types/storeApiTypes.js").components["schemas"]["Category"][];
18
+ cmsPage?: import("../types/storeApiTypes.js").components["schemas"]["CmsPage"];
19
+ cmsPageId?: string;
20
+ cmsPageIdSwitched?: boolean;
21
+ cmsPageVersionId?: string;
22
+ createdAt: string;
23
+ customEntityTypeId?: string;
24
+ customFields?: import("../types/storeApiTypes.js").GenericRecord;
25
+ description?: string;
26
+ displayNestedProducts: boolean;
27
+ externalLink?: string;
28
+ id: string;
29
+ internalLink?: string;
30
+ keywords?: string;
31
+ level?: number;
32
+ linkNewTab?: boolean;
33
+ linkType?: string;
34
+ media?: import("../types/storeApiTypes.js").components["schemas"]["Media"];
35
+ mediaId?: string;
36
+ metaDescription?: string;
37
+ metaTitle?: string;
38
+ name: string;
39
+ parent?: import("../types/storeApiTypes.js").components["schemas"]["Category"];
40
+ parentId?: string;
41
+ parentVersionId?: string;
42
+ path?: string;
43
+ productAssignmentType: string;
44
+ seoUrls?: import("../types/storeApiTypes.js").components["schemas"]["SeoUrl"][];
45
+ tags?: import("../types/storeApiTypes.js").components["schemas"]["Tag"][];
46
+ translated: {
47
+ afterCategoryId?: string;
48
+ afterCategoryVersionId?: string;
49
+ apiAlias?: string;
50
+ breadcrumb: string[];
51
+ cmsPageId?: string;
52
+ cmsPageVersionId?: string;
53
+ customEntityTypeId?: string;
54
+ description?: string;
55
+ externalLink?: string;
56
+ internalLink?: string;
57
+ keywords?: string;
58
+ linkType?: string;
59
+ mediaId?: string;
60
+ metaDescription?: string;
61
+ metaTitle?: string;
62
+ name?: string;
63
+ parentId?: string;
64
+ parentVersionId?: string;
65
+ path?: string;
66
+ productAssignmentType?: string;
67
+ type?: string;
68
+ versionId?: string;
69
+ };
70
+ type: string;
71
+ updatedAt?: string;
72
+ versionId?: string;
73
+ visible?: boolean;
74
+ visibleChildCount?: number;
75
+ }[];
@@ -0,0 +1,11 @@
1
+ export const flattenCategories = (categories, parent) => {
2
+ const flattenedCategories = [];
3
+ for (const category of categories) {
4
+ category.parentId = parent?.id;
5
+ flattenedCategories.push(category);
6
+ if (category.children) {
7
+ flattenedCategories.push(...flattenCategories(category.children, category));
8
+ }
9
+ }
10
+ return flattenedCategories;
11
+ };