@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,39 @@
1
+ import type { MintSessionHandoffParams } from './sessionHandoff.js';
2
+ /** Outcome of the checkout handoff decision: a redirect target, or a fail-closed error. */
3
+ export type CheckoutPlan = {
4
+ kind: 'redirect';
5
+ url: string;
6
+ } | {
7
+ kind: 'error';
8
+ statusCode: number;
9
+ statusMessage: string;
10
+ cause?: unknown;
11
+ };
12
+ export interface ResolveCheckoutDeps {
13
+ config: {
14
+ endpoint: string;
15
+ accessToken: string;
16
+ storefrontUrl?: string;
17
+ checkoutLoginCallbackUrl?: string;
18
+ checkoutLogoutCallbackUrl?: string;
19
+ };
20
+ /** Shopware context token from the request cookie (absent for a shopper with no cart yet). */
21
+ contextToken: string | null | undefined;
22
+ /** Request origin used for the login/logout success callbacks. */
23
+ origin: string;
24
+ /** Injected minter (real: {@link mintSessionHandoffCode}) — keeps this decision logic pure/testable. */
25
+ mint: (params: MintSessionHandoffParams) => Promise<string>;
26
+ }
27
+ /**
28
+ * Decide where a checkout click should send the shopper. Fail-closed and free of I/O beyond the
29
+ * injected `mint` call, so the whole outcome matrix is unit-testable without an h3 event:
30
+ *
31
+ * - no context cookie (empty cart) → redirect to the laioutr frontend root, no mint;
32
+ * - no `storefrontUrl` (misconfig) → 500, no mint;
33
+ * - mint succeeds → redirect to `connect-session?code=…`;
34
+ * - mint throws (e.g. callback not allowlisted) → 502.
35
+ *
36
+ * The Shopware storefront is only ever navigated to for an actual checkout handoff — never as a
37
+ * fallback. No branch mutates the cart.
38
+ */
39
+ export declare const resolveCheckout: (deps: ResolveCheckoutDeps) => Promise<CheckoutPlan>;
@@ -0,0 +1,24 @@
1
+ import { buildConnectSessionUrl } from "./checkoutUrl.js";
2
+ import { CHECKOUT_REDIRECT_ROUTE } from "../const/checkout.js";
3
+ export const resolveCheckout = async (deps) => {
4
+ const { config, contextToken, origin, mint } = deps;
5
+ if (!contextToken) {
6
+ return { kind: "redirect", url: "/" };
7
+ }
8
+ if (!config.storefrontUrl) {
9
+ return { kind: "error", statusCode: 500, statusMessage: "Checkout is not configured" };
10
+ }
11
+ try {
12
+ const code = await mint({
13
+ endpoint: config.endpoint,
14
+ accessToken: config.accessToken,
15
+ contextToken,
16
+ loginSuccessCallback: config.checkoutLoginCallbackUrl ?? origin,
17
+ logoutSuccessCallback: config.checkoutLogoutCallbackUrl ?? origin,
18
+ redirectRoute: CHECKOUT_REDIRECT_ROUTE
19
+ });
20
+ return { kind: "redirect", url: buildConnectSessionUrl({ storefrontUrl: config.storefrontUrl, code }) };
21
+ } catch (cause) {
22
+ return { kind: "error", statusCode: 502, statusMessage: "Checkout handoff failed", cause };
23
+ }
24
+ };
@@ -0,0 +1,27 @@
1
+ export interface MintSessionHandoffParams {
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
+ /** Context token to hand off (sent as the `sw-context-token` header). */
7
+ contextToken: string;
8
+ /** Allowlist-validated URL the storefront returns to after a login inside checkout. */
9
+ loginSuccessCallback: string;
10
+ /** Allowlist-validated URL the storefront returns to after a logout inside checkout. */
11
+ logoutSuccessCallback: string;
12
+ /** Internal Shopware route the handoff lands on after redeem. */
13
+ redirectRoute: string;
14
+ }
15
+ /**
16
+ * Mint a single-use session-handoff code against the `LaioutrConnector` plugin's
17
+ * `POST /store-api/laioutr/session-handoff` endpoint.
18
+ *
19
+ * Called server-to-server: laioutr holds both the sales-channel access key and the
20
+ * shopper's `sw-context-token` (http-only cookie), so the raw token never reaches
21
+ * the browser. The returned code is opaque, single-use, and expires in ~60s.
22
+ *
23
+ * @throws with the plugin's rejection reason when the mint responds non-2xx or without a `code`.
24
+ * A `400 "Callback domain is not allowed"` means the callback origin is not in the plugin's
25
+ * `callbackDomainWildcard` allowlist.
26
+ */
27
+ export declare const mintSessionHandoffCode: (params: MintSessionHandoffParams) => Promise<string>;
@@ -0,0 +1,29 @@
1
+ const describeMintError = (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 mintSessionHandoffCode = async (params) => {
7
+ const base = params.endpoint.replace(/\/+$/, "");
8
+ let res;
9
+ try {
10
+ res = await $fetch(`${base}/laioutr/session-handoff`, {
11
+ method: "POST",
12
+ headers: {
13
+ "sw-access-key": params.accessToken,
14
+ "sw-context-token": params.contextToken
15
+ },
16
+ body: {
17
+ "login-success-callback": params.loginSuccessCallback,
18
+ "logout-success-callback": params.logoutSuccessCallback,
19
+ "redirect-route": params.redirectRoute
20
+ }
21
+ });
22
+ } catch (err) {
23
+ throw new Error(`session-handoff mint rejected: ${describeMintError(err)}`);
24
+ }
25
+ if (!res?.code) {
26
+ throw new Error("session-handoff mint returned no code");
27
+ }
28
+ return res.code;
29
+ };
@@ -0,0 +1,3 @@
1
+ import type { components } from '../types/storeApiTypes.js';
2
+ import type { AvailableSorting } from '@laioutr-core/orchestr/types';
3
+ export declare const mapShopwareSortingToOrchestr: (sortings: components["schemas"]["ProductListingResult"]["availableSortings"]) => AvailableSorting[];
@@ -0,0 +1,7 @@
1
+ export const mapShopwareSortingToOrchestr = (sortings) => {
2
+ const sortedSortings = [...sortings].sort((a, b) => a.priority - b.priority);
3
+ return sortedSortings.map((sorting) => ({
4
+ key: sorting.key,
5
+ label: sorting.translated.label
6
+ }));
7
+ };
@@ -0,0 +1,20 @@
1
+ import type { PageIndexEntry } from '@laioutr-core/core-types/orchestr';
2
+ /** Shopware's store API caps `limit` at MAX_LIMIT; a larger value is rejected with a 400. */
3
+ export declare const SHOPWARE_MAX_LIMIT = 100;
4
+ /**
5
+ * Build the page-fetch closure `paginate` drives for a store-API list endpoint paged by number.
6
+ *
7
+ * Takes a read thunk rather than an endpoint name so the caller keeps the client's per-operation
8
+ * typing, and stays free of `#imports` so it remains unit-testable — the handler owns `paginate`.
9
+ */
10
+ export declare const storeApiPageFetcher: <TItem>(readPage: (args: {
11
+ page: number;
12
+ limit: number;
13
+ }) => Promise<{
14
+ elements?: TItem[];
15
+ }>, toEntry: (item: TItem) => PageIndexEntry, batchSize: number) => ({ cursor }: {
16
+ cursor: string | undefined;
17
+ }) => Promise<{
18
+ entries: PageIndexEntry[];
19
+ nextCursor: string | undefined;
20
+ }>;
@@ -0,0 +1,10 @@
1
+ export const SHOPWARE_MAX_LIMIT = 100;
2
+ export const storeApiPageFetcher = (readPage, toEntry, batchSize) => async ({ cursor }) => {
3
+ const page = cursor ? Number(cursor) : 1;
4
+ const elements = (await readPage({ page, limit: batchSize })).elements ?? [];
5
+ return {
6
+ entries: elements.map(toEntry),
7
+ // A short page ends the walk; an enumeration that divides exactly costs one extra empty read.
8
+ nextCursor: elements.length < batchSize ? void 0 : String(page + 1)
9
+ };
10
+ };
@@ -0,0 +1,7 @@
1
+ export declare const swTranslatedRaw: (rawEntity: any, key: PropertyKey) => any;
2
+ type SwTranslatable = {
3
+ translated?: any;
4
+ };
5
+ type SwTranslatableKeys<TEntity extends SwTranslatable> = keyof Exclude<TEntity['translated'], undefined>;
6
+ export declare function swTranslated<TEntity extends SwTranslatable, TKey extends SwTranslatableKeys<TEntity> & keyof TEntity>(rawEntity: TEntity | undefined, key: TKey & keyof TEntity): TEntity[TKey];
7
+ export {};
@@ -0,0 +1,4 @@
1
+ export const swTranslatedRaw = (rawEntity, key) => rawEntity?.translated?.[key] ?? rawEntity?.[key];
2
+ export function swTranslated(rawEntity, key) {
3
+ return swTranslatedRaw(rawEntity, key);
4
+ }
@@ -0,0 +1,20 @@
1
+ import { SwSystemEntities } from './getSystemEntities.js';
2
+ import { ClientEnv } from '@laioutr-core/orchestr/types';
3
+ export declare const getCurrentSystemEntities: (systemIds: SwSystemEntities, clientEnv: ClientEnv) => {
4
+ locale: {
5
+ id: string;
6
+ languageId: string;
7
+ iso: string;
8
+ };
9
+ currency: {
10
+ id: string;
11
+ name: string;
12
+ iso: string;
13
+ };
14
+ country: {
15
+ id: string;
16
+ iso: string | undefined;
17
+ iso3: string | undefined;
18
+ name: string;
19
+ };
20
+ };
@@ -0,0 +1,39 @@
1
+ const findBestLocale = (swLocales, clientLocale) => {
2
+ if (swLocales.length === 0) {
3
+ throw new Error("No locales available");
4
+ }
5
+ const normalizeLocale = (locale) => locale.replace("_", "-").toLowerCase();
6
+ const normalizedClientLocale = normalizeLocale(clientLocale);
7
+ const normalizedSwLocales = swLocales.map((locale) => ({
8
+ ...locale,
9
+ iso: normalizeLocale(locale.iso)
10
+ }));
11
+ const exactMatch = normalizedSwLocales.find((locale) => locale.iso === normalizedClientLocale);
12
+ if (exactMatch) {
13
+ return swLocales.find((locale) => locale.id === exactMatch.id);
14
+ }
15
+ const clientLanguage = normalizedClientLocale.split("-")[0];
16
+ const languageMatch = normalizedSwLocales.find((locale) => {
17
+ const swLanguage = locale.iso.split("-")[0];
18
+ return swLanguage === clientLanguage;
19
+ });
20
+ if (languageMatch) {
21
+ return swLocales.find((locale) => locale.id === languageMatch.id);
22
+ }
23
+ const englishMatch = normalizedSwLocales.find((locale) => locale.iso.startsWith("en"));
24
+ if (englishMatch) {
25
+ return swLocales.find((locale) => locale.id === englishMatch.id);
26
+ }
27
+ return swLocales[0];
28
+ };
29
+ export const getCurrentSystemEntities = (systemIds, clientEnv) => {
30
+ const clientLocale = new Intl.Locale(clientEnv.locale);
31
+ const locale = findBestLocale(systemIds.locales, clientEnv.locale);
32
+ const currency = systemIds.currencies.find((currency2) => currency2.iso === clientEnv.currency) ?? systemIds.currencies[0];
33
+ const country = systemIds.countries.find((country2) => country2.iso === clientLocale.region) ?? systemIds.countries[0];
34
+ return {
35
+ locale,
36
+ currency,
37
+ country
38
+ };
39
+ };
@@ -0,0 +1,31 @@
1
+ import { StorefrontClient } from '../../types/shopware.js';
2
+ export type SwSystemEntities = Awaited<ReturnType<typeof getSystemEntities>>;
3
+ export type SwSystemLocale = SwSystemEntities['locales'][number];
4
+ /**
5
+ * Shopware wants us to send ids for certain system entities.
6
+ * This function fetches all of the relevant ones and returns them aggregated for further use.
7
+ */
8
+ export declare const getSystemEntities: (client: StorefrontClient) => Promise<{
9
+ currencies: {
10
+ id: string;
11
+ name: string;
12
+ iso: string;
13
+ }[];
14
+ salutations: {
15
+ id: string;
16
+ key: string;
17
+ displayName: string;
18
+ }[];
19
+ countries: {
20
+ id: string;
21
+ iso: string | undefined;
22
+ iso3: string | undefined;
23
+ name: string;
24
+ }[];
25
+ locales: {
26
+ id: string;
27
+ languageId: string;
28
+ iso: string;
29
+ }[];
30
+ }>;
31
+ export declare const getCachedSystemEntities: (client: StorefrontClient) => Promise<any>;
@@ -0,0 +1,47 @@
1
+ import { useRuntimeConfig, useUserlandCache } from "#imports";
2
+ import { swTranslated } from "../swTranslated.js";
3
+ export const getSystemEntities = async (client) => {
4
+ const rawCurrencies = await client.invoke("readCurrency post /currency");
5
+ const currencies = rawCurrencies.data.map((currency) => ({
6
+ id: currency.id,
7
+ name: swTranslated(currency, "name"),
8
+ iso: swTranslated(currency, "isoCode")
9
+ }));
10
+ const rawSalutations = await client.invoke("readSalutation post /salutation");
11
+ const salutations = rawSalutations.data.elements?.map((salutation) => ({
12
+ id: salutation.id,
13
+ key: salutation.salutationKey,
14
+ displayName: swTranslated(salutation, "displayName")
15
+ })) ?? [];
16
+ const rawCountries = await client.invoke("readCountry post /country");
17
+ const countries = rawCountries.data.elements?.map((country) => ({
18
+ id: country.id,
19
+ iso: country.iso,
20
+ iso3: country.iso3,
21
+ name: swTranslated(country, "name")
22
+ })) ?? [];
23
+ const rawLanguages = await client.invoke("readLanguages post /language");
24
+ const locales = rawLanguages.data.elements?.map((language) => ({
25
+ id: language.localeId,
26
+ languageId: language.id,
27
+ iso: swTranslated(language.locale, "code") ?? swTranslated(language.translationCode, "code")
28
+ })) ?? [];
29
+ return {
30
+ currencies,
31
+ salutations,
32
+ countries,
33
+ locales
34
+ };
35
+ };
36
+ const SYSTEM_ENTITIES_TTL = 60 * 60 * 24;
37
+ export const getCachedSystemEntities = async (client) => {
38
+ const accessToken = useRuntimeConfig()["@laioutr/app-shopware"].accessToken;
39
+ const cache = useUserlandCache(`shopware:${accessToken}:system-entities`);
40
+ const cachedSystemEntities = await cache.getItem("default");
41
+ if (cachedSystemEntities) {
42
+ return cachedSystemEntities;
43
+ }
44
+ const systemEntities = await getSystemEntities(client);
45
+ await cache.setItem("default", systemEntities, { ttl: SYSTEM_ENTITIES_TTL });
46
+ return systemEntities;
47
+ };
@@ -0,0 +1,11 @@
1
+ import { StorefrontClient } from '../types/shopware.js';
2
+ type SeoUrlType = 'product' | 'category';
3
+ interface SeoEntry {
4
+ type: SeoUrlType;
5
+ id: string;
6
+ matchedPath: string;
7
+ }
8
+ export declare const useSeoResolver: (storefrontClient: StorefrontClient) => {
9
+ resolve: (type: SeoUrlType, slug: string) => Promise<SeoEntry | undefined>;
10
+ };
11
+ export {};
@@ -0,0 +1,70 @@
1
+ import { extractShopwareId, isShopwareId } from "./isShopwareId.js";
2
+ import { isSlugMatchingSeoPath } from "./mappers/slugMapper.js";
3
+ import { useUserlandCache } from "#imports";
4
+ const typeToRouteNames = {
5
+ product: ["frontend.detail.page"],
6
+ category: ["frontend.navigation.page", "frontend.landing.page"]
7
+ };
8
+ const SEO_ENTRY_TTL = 60 * 60 * 24;
9
+ export const useSeoResolver = (storefrontClient) => {
10
+ const cache = useUserlandCache("shopware/seo-urls");
11
+ const resolve = async (type, slug) => {
12
+ const languageId = storefrontClient.defaultHeaders["sw-language-id"] ?? "default";
13
+ const cacheKey = `${languageId}:${type}-${slug}`;
14
+ const cachedSlug = await cache.getItem(cacheKey);
15
+ if (cachedSlug) {
16
+ return cachedSlug;
17
+ }
18
+ const shopwareId = extractShopwareId(slug);
19
+ if (shopwareId) {
20
+ return {
21
+ type,
22
+ id: shopwareId,
23
+ matchedPath: slug
24
+ };
25
+ }
26
+ const response = await storefrontClient.invoke("readSeoUrl post /seo-url", {
27
+ body: {
28
+ filter: [
29
+ {
30
+ type: "multi",
31
+ operator: "and",
32
+ queries: [
33
+ {
34
+ type: "multi",
35
+ operator: "or",
36
+ queries: [
37
+ { field: "seoPathInfo", type: "contains", value: slug },
38
+ { field: "seoPathInfo", type: "equals", value: slug }
39
+ ]
40
+ },
41
+ { field: "routeName", type: "equalsAny", value: typeToRouteNames[type] }
42
+ ]
43
+ }
44
+ ]
45
+ }
46
+ });
47
+ const exactMatch = response.data.elements.find((element) => element.seoPathInfo === slug);
48
+ const bestMatch = exactMatch ?? response.data.elements.find((element) => isSlugMatchingSeoPath(slug, element.seoPathInfo));
49
+ if (!bestMatch && isShopwareId(slug)) {
50
+ return {
51
+ type,
52
+ id: slug,
53
+ matchedPath: slug
54
+ };
55
+ }
56
+ if (!bestMatch) {
57
+ return void 0;
58
+ }
59
+ const entry = {
60
+ type,
61
+ id: bestMatch.foreignKey,
62
+ matchedPath: bestMatch.seoPathInfo
63
+ };
64
+ await cache.setItem(cacheKey, entry, { ttl: SEO_ENTRY_TTL });
65
+ return entry;
66
+ };
67
+ return {
68
+ resolve
69
+ };
70
+ };
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../../.nuxt/tsconfig.server.json"
3
+ }
@@ -0,0 +1,29 @@
1
+ import { createAPIClient } from '@shopware/api-client';
2
+ import type { components, operations } from './storeApiTypes.js';
3
+ export type ShopwareProduct = components['schemas']['Product'];
4
+ export type ShopwareProductReview = components['schemas']['ProductReview'];
5
+ export type ShopwareCategory = components['schemas']['Category'];
6
+ export type ShopwareFilters = components['schemas']['Filters'];
7
+ export type ShopwareManufacturer = components['schemas']['ProductManufacturer'];
8
+ export type ShopwareSeoUrl = components['schemas']['SeoUrl'];
9
+ export type StorefrontClient = ReturnType<typeof createAPIClient<operations>>;
10
+ export type ShopwareIncludesQuery = components['schemas']['Include'];
11
+ export type ShopwareAssociationsQuery = components['schemas']['Association'];
12
+ export type WithSeoUrl = {
13
+ seoUrls?: ShopwareSeoUrl[];
14
+ };
15
+ export type ShopwareExtensions = {
16
+ extensions?: {
17
+ completionResult?: Record<string, string>;
18
+ multiSuggestResult?: {
19
+ suggestResults?: {
20
+ product_manufacturer?: {
21
+ elements: Record<string, ShopwareManufacturer>;
22
+ };
23
+ category?: {
24
+ elements: Record<string, ShopwareCategory>;
25
+ };
26
+ };
27
+ };
28
+ };
29
+ };
File without changes