@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,12 @@
1
+ /**
2
+ * Build the redeem URL against the `LaioutrConnector` plugin's
3
+ * `/laioutr/connect-session` endpoint.
4
+ *
5
+ * Only the opaque, single-use `code` (minted server-to-server via
6
+ * {@link mintSessionHandoffCode}) travels through the browser — the raw
7
+ * `sw-context-token` never appears in a URL, history, or `Referer`.
8
+ */
9
+ export declare const buildConnectSessionUrl: (params: {
10
+ storefrontUrl: string;
11
+ code: string;
12
+ }) => string;
@@ -0,0 +1,5 @@
1
+ export const buildConnectSessionUrl = (params) => {
2
+ const base = params.storefrontUrl.replace(/\/+$/, "");
3
+ const query = new URLSearchParams({ code: params.code });
4
+ return `${base}/laioutr/connect-session?${query.toString()}`;
5
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Cookie options for the `sw-context-token` cookie on the laioutr domain. `httpOnly` so the
3
+ * token is never readable by client JS (an XSS on laioutr can no longer exfiltrate it).
4
+ * Pure — no `#imports` — so it is unit-testable.
5
+ */
6
+ export declare const contextTokenCookieOptions: () => {
7
+ maxAge: number;
8
+ path: string;
9
+ sameSite: "lax";
10
+ httpOnly: true;
11
+ };
@@ -0,0 +1,7 @@
1
+ export const contextTokenCookieOptions = () => ({
2
+ maxAge: 60 * 60 * 24 * 365,
3
+ // 1 year
4
+ path: "/",
5
+ sameSite: "lax",
6
+ httpOnly: true
7
+ });
@@ -0,0 +1,38 @@
1
+ import { AvailableFilter, QueryWireRequestFilter } from '#orchestr/types';
2
+ export type ShopwareAggregationKey = 'price' | 'shipping-free' | 'manufacturer';
3
+ export interface ShopwareAggregation {
4
+ name: string;
5
+ min?: number;
6
+ max?: number;
7
+ entities?: Array<{
8
+ id: string;
9
+ name: string;
10
+ }>;
11
+ }
12
+ export type ShopwareAggregations = Record<ShopwareAggregationKey, ShopwareAggregation>;
13
+ export declare const mapShopwareAggregationToAvailableFilters: (facets: ShopwareAggregations) => AvailableFilter[];
14
+ export declare const mapSelectedFiltersToShopwareFilters: (filters: QueryWireRequestFilter, currency: string) => {
15
+ swFilters: ({
16
+ field: string;
17
+ type: "contains" | "equalsAny" | "prefix" | "suffix";
18
+ value: string;
19
+ } | {
20
+ field: string;
21
+ type: "equals";
22
+ value: string | number | boolean | null;
23
+ } | {
24
+ operator: "AND" | "and" | "OR" | "or";
25
+ queries: import("../types/storeApiTypes.js").components["schemas"]["Filters"];
26
+ type: "multi" | "not";
27
+ } | {
28
+ field: string;
29
+ parameters: {
30
+ gt?: number;
31
+ gte?: number;
32
+ lt?: number;
33
+ lte?: number;
34
+ };
35
+ type: "range";
36
+ })[];
37
+ swBuiltInFilters: Record<"manufacturer" | "shipping-free" | "min-price" | "max-price", string | number | boolean | undefined>;
38
+ };
@@ -0,0 +1,79 @@
1
+ import { isRangeFilter } from "#orchestr/types";
2
+ import { Money } from "@screeny05/ts-money";
3
+ export const mapShopwareAggregationToAvailableFilters = (facets) => {
4
+ const filters = [];
5
+ const listFacets = ["manufacturer"];
6
+ const rangeFacets = ["price"];
7
+ const booleanFacets = ["shipping-free"];
8
+ for (const facet in facets) {
9
+ const facetKey = facet;
10
+ const facetObj = facets[facet];
11
+ if (listFacets.includes(facetKey)) {
12
+ filters.push({
13
+ id: facetObj.name,
14
+ label: facetObj.name,
15
+ type: "list",
16
+ presentation: "text",
17
+ values: (facetObj.entities ?? []).map((entity) => ({ id: entity.id, label: entity.name }))
18
+ });
19
+ } else if (rangeFacets.includes(facetKey)) {
20
+ filters.push({
21
+ id: facetObj.name,
22
+ label: facetObj.name,
23
+ type: "range",
24
+ min: 0,
25
+ max: facetObj.max ?? 0
26
+ });
27
+ } else if (booleanFacets.includes(facetKey)) {
28
+ filters.push({
29
+ id: facetObj.name,
30
+ label: facetObj.name,
31
+ type: "boolean"
32
+ });
33
+ }
34
+ }
35
+ return filters;
36
+ };
37
+ export const mapSelectedFiltersToShopwareFilters = (filters, currency) => {
38
+ const swFilters = [];
39
+ const swBuiltInFilters = {};
40
+ for (const filter in filters) {
41
+ const filterObj = filters[filter];
42
+ if (isRangeFilter(filterObj)) {
43
+ if (filter === "price") {
44
+ swBuiltInFilters["min-price"] = filterObj.min === void 0 ? void 0 : new Money(filterObj.min, currency).toDecimal();
45
+ swBuiltInFilters["max-price"] = filterObj.max === void 0 ? void 0 : new Money(filterObj.max, currency).toDecimal();
46
+ } else {
47
+ swFilters.push({
48
+ type: "range",
49
+ field: filter,
50
+ parameters: {
51
+ lte: filterObj.max,
52
+ gte: filterObj.min
53
+ }
54
+ });
55
+ }
56
+ } else if (typeof filterObj === "boolean") {
57
+ if (filter === "shipping-free") {
58
+ swBuiltInFilters["shipping-free"] = filterObj;
59
+ } else {
60
+ swFilters.push({
61
+ type: "equals",
62
+ field: filter,
63
+ value: filterObj
64
+ });
65
+ }
66
+ } else {
67
+ if (filter === "manufacturer") {
68
+ swBuiltInFilters.manufacturer = filterObj.join("|");
69
+ } else {
70
+ swFilters.push({
71
+ type: "equalsAny",
72
+ field: filter,
73
+ value: filterObj.join("|")
74
+ });
75
+ }
76
+ }
77
+ }
78
+ return { swFilters, swBuiltInFilters };
79
+ };
@@ -0,0 +1,137 @@
1
+ import { StorefrontClient } from '../types/shopware.js';
2
+ export declare const fetchAllProducts: (storefrontClient: StorefrontClient, { productIds, loadVariants }: {
3
+ productIds: string[];
4
+ loadVariants: boolean;
5
+ }) => Promise<{
6
+ active?: boolean;
7
+ apiAlias: "product";
8
+ available?: boolean;
9
+ availableStock?: number;
10
+ calculatedCheapestPrice?: {
11
+ apiAlias?: "calculated_cheapest_price";
12
+ hasRange?: boolean;
13
+ listPrice?: import("../types/storeApiTypes.js").components["schemas"]["ListPrice"] | null;
14
+ quantity?: number;
15
+ referencePrice?: import("../types/storeApiTypes.js").components["schemas"]["ReferencePrice"] | null;
16
+ regulationPrice?: {
17
+ price: number;
18
+ } | null;
19
+ totalPrice?: number;
20
+ unitPrice?: number;
21
+ variantId?: string | null;
22
+ };
23
+ calculatedMaxPurchase?: number;
24
+ calculatedPrice: import("../types/storeApiTypes.js").components["schemas"]["CalculatedPrice"];
25
+ calculatedPrices: import("../types/storeApiTypes.js").components["schemas"]["CalculatedPrice"][];
26
+ canonicalProduct?: import("../types/storeApiTypes.js").components["schemas"]["Product"];
27
+ canonicalProductId?: string;
28
+ canonicalProductVersionId?: string;
29
+ categories?: import("../types/storeApiTypes.js").components["schemas"]["Category"][];
30
+ categoriesRo?: import("../types/storeApiTypes.js").components["schemas"]["Category"][];
31
+ categoryIds?: readonly string[];
32
+ categoryTree?: readonly string[];
33
+ childCount?: number;
34
+ children?: import("../types/storeApiTypes.js").components["schemas"]["Product"][];
35
+ cmsPage?: import("../types/storeApiTypes.js").components["schemas"]["CmsPage"];
36
+ cmsPageId?: string;
37
+ cmsPageVersionId?: string;
38
+ configuratorSettings?: import("../types/storeApiTypes.js").components["schemas"]["ProductConfiguratorSetting"][];
39
+ cover?: import("../types/storeApiTypes.js").components["schemas"]["ProductMedia"];
40
+ coverId?: string;
41
+ createdAt: string;
42
+ crossSellings?: import("../types/storeApiTypes.js").components["schemas"]["ProductCrossSelling"][];
43
+ customFields?: import("../types/storeApiTypes.js").GenericRecord;
44
+ deliveryTime?: import("../types/storeApiTypes.js").components["schemas"]["DeliveryTime"];
45
+ deliveryTimeId?: string;
46
+ description?: string;
47
+ displayGroup?: string;
48
+ downloads?: import("../types/storeApiTypes.js").components["schemas"]["ProductDownload"][];
49
+ ean?: string;
50
+ height?: number;
51
+ id: string;
52
+ isCloseout?: boolean;
53
+ isNew?: boolean;
54
+ keywords?: string;
55
+ length?: number;
56
+ mainCategories?: import("../types/storeApiTypes.js").components["schemas"]["MainCategory"][];
57
+ manufacturer?: import("../types/storeApiTypes.js").components["schemas"]["ProductManufacturer"];
58
+ manufacturerId?: string;
59
+ manufacturerNumber?: string;
60
+ markAsTopseller?: boolean;
61
+ maxPurchase?: number;
62
+ media?: import("../types/storeApiTypes.js").components["schemas"]["ProductMedia"][];
63
+ metaDescription?: string;
64
+ metaTitle?: string;
65
+ minPurchase?: number;
66
+ name: string;
67
+ optionIds?: readonly string[];
68
+ options?: import("../types/storeApiTypes.js").components["schemas"]["PropertyGroupOption"][];
69
+ packUnit?: string;
70
+ packUnitPlural?: string;
71
+ parent?: import("../types/storeApiTypes.js").components["schemas"]["Product"];
72
+ parentId?: string;
73
+ parentVersionId?: string;
74
+ productManufacturerVersionId?: string;
75
+ productMediaVersionId?: string;
76
+ productNumber: string;
77
+ productReviews?: import("../types/storeApiTypes.js").components["schemas"]["ProductReview"][];
78
+ properties?: import("../types/storeApiTypes.js").components["schemas"]["PropertyGroupOption"][];
79
+ propertyIds?: readonly string[];
80
+ purchaseSteps?: number;
81
+ purchaseUnit?: number;
82
+ ratingAverage?: number;
83
+ referenceUnit?: number;
84
+ releaseDate?: string;
85
+ restockTime?: number;
86
+ sales?: number;
87
+ seoCategory: import("../types/storeApiTypes.js").components["schemas"]["Category"];
88
+ seoUrls?: import("../types/storeApiTypes.js").components["schemas"]["SeoUrl"][];
89
+ shippingFree?: boolean;
90
+ sortedProperties?: import("../types/storeApiTypes.js").GenericRecord;
91
+ states?: readonly string[];
92
+ stock: number;
93
+ streamIds?: readonly string[];
94
+ streams?: import("../types/storeApiTypes.js").components["schemas"]["ProductStream"][];
95
+ tagIds?: readonly string[];
96
+ tags?: import("../types/storeApiTypes.js").components["schemas"]["Tag"][];
97
+ tax?: import("../types/storeApiTypes.js").components["schemas"]["Tax"];
98
+ taxId: string;
99
+ translated: {
100
+ apiAlias?: string;
101
+ canonicalProductId?: string;
102
+ canonicalProductVersionId?: string;
103
+ cmsPageId?: string;
104
+ cmsPageVersionId?: string;
105
+ coverId?: string;
106
+ deliveryTimeId?: string;
107
+ description?: string;
108
+ displayGroup?: string;
109
+ ean?: string;
110
+ keywords?: string;
111
+ manufacturerId?: string;
112
+ manufacturerNumber?: string;
113
+ metaDescription?: string;
114
+ metaTitle?: string;
115
+ name?: string;
116
+ packUnit?: string;
117
+ packUnitPlural?: string;
118
+ parentId?: string;
119
+ parentVersionId?: string;
120
+ productManufacturerVersionId?: string;
121
+ productMediaVersionId?: string;
122
+ productNumber?: string;
123
+ releaseDate?: string;
124
+ taxId?: string;
125
+ unitId?: string;
126
+ versionId?: string;
127
+ };
128
+ unit?: import("../types/storeApiTypes.js").components["schemas"]["Unit"];
129
+ unitId?: string;
130
+ updatedAt?: string;
131
+ variantListingConfig?: {
132
+ displayParent?: boolean;
133
+ } | null;
134
+ versionId?: string;
135
+ weight?: number;
136
+ width?: number;
137
+ }[]>;
@@ -0,0 +1,16 @@
1
+ import { resolveProductFields } from "../orchestr-helper/requestedFields.js";
2
+ export const fetchAllProducts = async (storefrontClient, { productIds, loadVariants }) => {
3
+ const fields = resolveProductFields({ loadVariants });
4
+ if (productIds.length === 0) {
5
+ return [];
6
+ }
7
+ const response = await storefrontClient.invoke("readProduct post /product", {
8
+ body: {
9
+ ids: productIds,
10
+ ...fields
11
+ }
12
+ });
13
+ const products = response.data.elements ?? [];
14
+ const all = [...products.flatMap((product) => product.children ?? []), ...products];
15
+ return all;
16
+ };
@@ -0,0 +1,10 @@
1
+ import { StorefrontClient } from '../types/shopware.js';
2
+ import { Schemas } from '../types/storeApiTypes.js';
3
+ /**
4
+ * Fetch the current session cart from the Shopware store-api.
5
+ *
6
+ * The cart is identified per unique context session (the `sw-context-token`
7
+ * header/cookie set by the client factory). Callers cache the result in the
8
+ * `cartFragmentToken` passthrough so a single request resolves the cart once.
9
+ */
10
+ export declare const getCart: (client: StorefrontClient) => Promise<Schemas["Cart"]>;
@@ -0,0 +1,4 @@
1
+ export const getCart = async (client) => {
2
+ const cart = await client.invoke("readCart get /checkout/cart");
3
+ return cart.data;
4
+ };
@@ -0,0 +1,4 @@
1
+ /** This is not absolutely correct, but it's a good enough approximation. */
2
+ export declare const isShopwareId: (id: string) => boolean;
3
+ /** Extract the shopware id from a string. E.g. fallback slugs of format "slug-<shopware-id>" */
4
+ export declare const extractShopwareId: (str: string) => string | undefined;
@@ -0,0 +1,2 @@
1
+ export const isShopwareId = (id) => /^[0-9a-f]{32}$/i.test(id);
2
+ export const extractShopwareId = (str) => str.match(/([0-9a-f]{32})$/i)?.[1];
@@ -0,0 +1,10 @@
1
+ import { ShopwareCategory, ShopwareProduct, ShopwareSeoUrl, WithSeoUrl } from '../../types/shopware.js';
2
+ /** Determine whether a given slug matches a seoPath */
3
+ export declare const isSlugMatchingSeoPath: (slug: string, seoPath: string) => boolean;
4
+ /** Return the canonical seoUrl or the first seoUrl if no canonical is available */
5
+ export declare const findCanonicalSeoUrl: (seoUrls: ShopwareSeoUrl[]) => ShopwareSeoUrl;
6
+ /** Map a generic entity with seoUrls to a slug */
7
+ export declare const getEntitySeoSlug: (rawEntity: WithSeoUrl) => string | undefined;
8
+ export declare const createFallbackSlug: (name: string, id: string) => string;
9
+ /** Get slug of a product. Falls back to sluggified name if no seoUrl is available */
10
+ export declare const entitySlug: (rawEntity: ShopwareProduct | ShopwareCategory) => string;
@@ -0,0 +1,9 @@
1
+ import slug from "slug";
2
+ import { swTranslated } from "../swTranslated.js";
3
+ const extractSlugFromPath = (path) => path.replace(/\/+$/, "");
4
+ const seoUrlToSlug = (seoUrl) => extractSlugFromPath(seoUrl.seoPathInfo);
5
+ export const isSlugMatchingSeoPath = (slug2, seoPath) => extractSlugFromPath(seoPath).toLowerCase() === extractSlugFromPath(slug2).toLowerCase();
6
+ export const findCanonicalSeoUrl = (seoUrls) => seoUrls.find((seoUrl) => seoUrl.isCanonical) ?? seoUrls[0];
7
+ export const getEntitySeoSlug = (rawEntity) => rawEntity.seoUrls && rawEntity.seoUrls.length > 0 ? seoUrlToSlug(findCanonicalSeoUrl(rawEntity.seoUrls)) : void 0;
8
+ export const createFallbackSlug = (name, id) => `${slug(name)}-${id}`;
9
+ export const entitySlug = (rawEntity) => getEntitySeoSlug(rawEntity) ?? createFallbackSlug(swTranslated(rawEntity, "name"), rawEntity.id);
@@ -0,0 +1,8 @@
1
+ import { Media, MediaSourceImage, MediaSourceVideo } from '@laioutr-core/core-types/common';
2
+ import { Schemas } from '../types/storeApiTypes.js';
3
+ type SwMedia = Schemas['Media'];
4
+ export declare const mediaToSrc: (media: SwMedia) => string;
5
+ export declare const mapMediaSourceImage: (media: SwMedia) => MediaSourceImage;
6
+ export declare const mapMediaSourceVideo: (media: SwMedia) => MediaSourceVideo;
7
+ export declare const mapMedia: (media: SwMedia) => Media;
8
+ export {};
@@ -0,0 +1,42 @@
1
+ import { FALLBACK_IMAGE, FALLBACK_IMAGE_URL } from "../const/fallbacks.js";
2
+ export const mediaToSrc = (media) => {
3
+ if ((!media.thumbnails || media.thumbnails.length === 0) && !media.url) {
4
+ return FALLBACK_IMAGE_URL;
5
+ }
6
+ const thumbnails = media.thumbnails ?? [];
7
+ const thumbnailSrc = thumbnails.map((thumb) => `${thumb.url} ${thumb.width}x${thumb.height}`);
8
+ const orgSrc = `${media.url} ${media.metaData?.width ?? 0}x${media.metaData?.height ?? 0}`;
9
+ return `${media.url}#${encodeURIComponent([...thumbnailSrc, orgSrc].join(", "))}`;
10
+ };
11
+ export const mapMediaSourceImage = (media) => {
12
+ const src = mediaToSrc(media);
13
+ if (src === FALLBACK_IMAGE_URL) {
14
+ return FALLBACK_IMAGE.sources[0];
15
+ }
16
+ return {
17
+ provider: "shopware",
18
+ width: media.metaData?.width ?? 100,
19
+ height: media.metaData?.height ?? 100,
20
+ src
21
+ };
22
+ };
23
+ export const mapMediaSourceVideo = (media) => ({
24
+ provider: "shopware",
25
+ src: media.url,
26
+ width: media.metaData?.width ?? 0,
27
+ height: media.metaData?.height ?? 0
28
+ });
29
+ export const mapMedia = (media) => {
30
+ const type = media.mimeType?.startsWith("video/") ? "video" : "image";
31
+ const source = type === "image" ? mapMediaSourceImage(media) : mapMediaSourceVideo(media);
32
+ return {
33
+ type,
34
+ // Shopware exposes both `alt` and `title`; prefer the explicit alt text and
35
+ // fall back to the title, treating blank/`null` as unset. This also keeps the
36
+ // value `string | undefined` (never `null`) — the canonical Media schema's `alt`
37
+ // rejects `null`, so an unset-alt asset would otherwise fail validation and be
38
+ // dropped by the media-library trust boundary (wrapProvider.sanitizeProviderItem).
39
+ alt: media.alt || media.title || void 0,
40
+ sources: [source]
41
+ };
42
+ };
@@ -0,0 +1,20 @@
1
+ import type { ShopwareCategory, ShopwareProduct } from '../types/shopware.js';
2
+ import type { PageIndexEntry } from '@laioutr-core/core-types/orchestr';
3
+ /**
4
+ * Shared by `query` entries and `locate` results so both describe a product page identically.
5
+ *
6
+ * `previewImage` is the cover media's **plain** URL, deliberately not `mapMedia`: its encoded srcset
7
+ * can't be dereferenced by the consumers that read `PageIndexEntry.meta.previewImage` (Cockpit picker,
8
+ * sitemaps, indexers), which all live outside the Nuxt image pipeline.
9
+ */
10
+ export declare const toProductPageMeta: (product: ShopwareProduct) => PageIndexEntry["meta"];
11
+ export declare const toProductPageEntry: (product: ShopwareProduct) => PageIndexEntry;
12
+ /**
13
+ * Shared by `list`/`search` entries and `locate` results so both describe a category page identically.
14
+ *
15
+ * `previewImage` is the category media's plain URL for the same reason the product mapper uses one:
16
+ * the consumers that read it run outside the Nuxt image pipeline and cannot dereference an encoded
17
+ * srcset.
18
+ */
19
+ export declare const toCategoryPageMeta: (category: ShopwareCategory) => PageIndexEntry["meta"];
20
+ export declare const toCategoryPageEntry: (category: ShopwareCategory) => PageIndexEntry;
@@ -0,0 +1,22 @@
1
+ import { entitySlug } from "./mappers/slugMapper.js";
2
+ import { swTranslated } from "./swTranslated.js";
3
+ export const toProductPageMeta = (product) => ({
4
+ title: swTranslated(product, "name") ?? product.id,
5
+ previewImage: product.cover?.media?.url,
6
+ lastModified: product.updatedAt ?? void 0
7
+ });
8
+ export const toProductPageEntry = (product) => ({
9
+ params: { slug: entitySlug(product) },
10
+ subject: { type: "Product", id: product.id },
11
+ meta: toProductPageMeta(product)
12
+ });
13
+ export const toCategoryPageMeta = (category) => ({
14
+ title: swTranslated(category, "name") ?? category.name,
15
+ previewImage: category.media?.url,
16
+ lastModified: category.updatedAt ?? void 0
17
+ });
18
+ export const toCategoryPageEntry = (category) => ({
19
+ params: { slug: entitySlug(category) },
20
+ subject: { type: "Category", id: category.id },
21
+ meta: toCategoryPageMeta(category)
22
+ });
@@ -0,0 +1,13 @@
1
+ import type { H3Event } from 'h3';
2
+ /**
3
+ * Persist / refresh the Shopware context-token cookie (httpOnly), then notify host-registered
4
+ * `shopware:context-token:changed` handlers so a project can mirror the token into its own
5
+ * session store. No-op when the response carries no token.
6
+ */
7
+ export declare const persistContextToken: (event: H3Event, token: string | null | undefined) => Promise<void>;
8
+ /**
9
+ * Clear the context-token cookie and notify host handlers with a null token, so a storefront
10
+ * logout ends laioutr's mirrored session too. The next store-api call re-bootstraps a guest
11
+ * context.
12
+ */
13
+ export declare const clearContextToken: (event: H3Event) => Promise<void>;
@@ -0,0 +1,12 @@
1
+ import { contextTokenCookieOptions } from "./contextTokenCookie.js";
2
+ import { deleteManagedCookie, setManagedCookie, useNitroApp } from "#imports";
3
+ import { CONTEXT_TOKEN_COOKIE } from "../const/cookieKeys.js";
4
+ export const persistContextToken = async (event, token) => {
5
+ if (!token) return;
6
+ setManagedCookie(event, CONTEXT_TOKEN_COOKIE, token, contextTokenCookieOptions());
7
+ await useNitroApp().hooks.callHook("shopware:context-token:changed", { event, token });
8
+ };
9
+ export const clearContextToken = async (event) => {
10
+ deleteManagedCookie(event, CONTEXT_TOKEN_COOKIE, { path: "/" });
11
+ await useNitroApp().hooks.callHook("shopware:context-token:changed", { event, token: null });
12
+ };
@@ -0,0 +1,14 @@
1
+ import type { PageIndexLocateResult } from '@laioutr-core/core-types/orchestr';
2
+ /**
3
+ * Shape a product `locate` result from a resolved product id.
4
+ *
5
+ * `undefined` when the product could not be resolved, so the endpoint answers `null`. Without `meta`,
6
+ * consumers fall back to the route params.
7
+ *
8
+ * Pass `slugsByLocale` — a BCP-47 locale → slug map — only when every one of the project's locales
9
+ * was looked up. A missing key asserts the product has no page in that locale, which drops its
10
+ * hreflang alternate and sends the locale switcher to the homepage; omit the argument entirely while
11
+ * only the current locale is resolved, so consumers treat the rest as unknown. Locales with an empty
12
+ * slug are dropped from a map that is passed.
13
+ */
14
+ export declare const buildProductLocate: (productId: string | undefined, meta?: PageIndexLocateResult["meta"], slugsByLocale?: Record<string, string | undefined>) => PageIndexLocateResult | undefined;
@@ -0,0 +1,9 @@
1
+ export const buildProductLocate = (productId, meta, slugsByLocale) => {
2
+ if (!productId) return void 0;
3
+ if (!slugsByLocale) return { subject: { type: "Product", id: productId }, meta };
4
+ const locales = {};
5
+ for (const [locale, slug] of Object.entries(slugsByLocale)) {
6
+ if (slug) locales[locale] = { params: { slug } };
7
+ }
8
+ return { subject: { type: "Product", id: productId }, meta, locales };
9
+ };
@@ -0,0 +1,10 @@
1
+ import type { StorefrontClient } from '../types/shopware.js';
2
+ import type { PageIndexLocateResult } from '@laioutr-core/core-types/orchestr';
3
+ /**
4
+ * Read a resolved category's page metadata for `pageIndex.locate`. Translations follow the client's
5
+ * `sw-language-id`, so the name comes back in the locale the lookup was made in.
6
+ *
7
+ * Swallows a missing category and a failed read — metadata is a labelling nicety and must never cost
8
+ * the caller the subject it already resolved.
9
+ */
10
+ export declare const readCategoryPageMeta: (storefrontClient: StorefrontClient, categoryId: string) => Promise<PageIndexLocateResult["meta"]>;
@@ -0,0 +1,19 @@
1
+ import { toCategoryPageMeta } from "./pageIndexEntries.js";
2
+ const metaCriteria = {
3
+ associations: { media: {} },
4
+ includes: {
5
+ category: ["id", "name", "translated", "updatedAt", "media"],
6
+ media: ["url"]
7
+ }
8
+ };
9
+ export const readCategoryPageMeta = async (storefrontClient, categoryId) => {
10
+ try {
11
+ const response = await storefrontClient.invoke("readCategoryList post /category", {
12
+ body: { ...metaCriteria, ids: [categoryId], limit: 1 }
13
+ });
14
+ const category = response.data.elements?.[0];
15
+ return category ? toCategoryPageMeta(category) : void 0;
16
+ } catch {
17
+ return void 0;
18
+ }
19
+ };
@@ -0,0 +1,10 @@
1
+ import type { StorefrontClient } from '../types/shopware.js';
2
+ import type { PageIndexLocateResult } from '@laioutr-core/core-types/orchestr';
3
+ /**
4
+ * Read a resolved product's page metadata for `pageIndex.locate`. Translations follow the client's
5
+ * `sw-language-id`, so the name comes back in the locale the lookup was made in.
6
+ *
7
+ * Swallows a missing product and a failed read — metadata is a labelling nicety and must never cost
8
+ * the caller the subject it already resolved.
9
+ */
10
+ export declare const readProductPageMeta: (storefrontClient: StorefrontClient, productId: string) => Promise<PageIndexLocateResult["meta"]>;
@@ -0,0 +1,20 @@
1
+ import { toProductPageMeta } from "./pageIndexEntries.js";
2
+ const metaCriteria = {
3
+ associations: { cover: {} },
4
+ includes: {
5
+ product: ["id", "name", "translated", "updatedAt", "cover"],
6
+ product_media: ["media"],
7
+ media: ["url"]
8
+ }
9
+ };
10
+ export const readProductPageMeta = async (storefrontClient, productId) => {
11
+ try {
12
+ const response = await storefrontClient.invoke("readProduct post /product", {
13
+ body: { ...metaCriteria, ids: [productId], limit: 1 }
14
+ });
15
+ const product = response.data.elements?.[0];
16
+ return product ? toProductPageMeta(product) : void 0;
17
+ } catch {
18
+ return void 0;
19
+ }
20
+ };
@@ -0,0 +1,23 @@
1
+ import type { AdoptSessionTokenParams } from './adoptSession.js';
2
+ export interface AdoptSessionDeps {
3
+ config: {
4
+ endpoint: string;
5
+ accessToken: string;
6
+ };
7
+ /** The `code` from a `laioutr:auth-changed` message; absent/empty means logout. */
8
+ code: string | null | undefined;
9
+ /** Injected {@link adoptSessionToken}. */
10
+ adopt: (params: AdoptSessionTokenParams) => Promise<string>;
11
+ /** Injected token persister (real: `persistContextToken` bound to the event). */
12
+ persist: (token: string) => Promise<void>;
13
+ /** Injected token clearer (real: `clearContextToken` bound to the event). */
14
+ clear: () => Promise<void>;
15
+ }
16
+ /**
17
+ * Decide how a `laioutr:auth-changed` message affects laioutr's stored session. Pure and
18
+ * free of `#imports` so the whole matrix is unit-testable: a `code` means login (redeem →
19
+ * persist); no `code` means logout (clear). The redeemed token stays server-side.
20
+ */
21
+ export declare const adoptSession: (deps: AdoptSessionDeps) => Promise<{
22
+ authenticated: boolean;
23
+ }>;
@@ -0,0 +1,13 @@
1
+ export const adoptSession = async (deps) => {
2
+ if (!deps.code) {
3
+ await deps.clear();
4
+ return { authenticated: false };
5
+ }
6
+ const token = await deps.adopt({
7
+ endpoint: deps.config.endpoint,
8
+ accessToken: deps.config.accessToken,
9
+ code: deps.code
10
+ });
11
+ await deps.persist(token);
12
+ return { authenticated: true };
13
+ };