@scayle/storefront-core 8.7.1 → 8.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @scayle/storefront-core
2
2
 
3
+ ## 8.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Extend `RpcContext` with an `rpcCall` property. This is a utility function that can be used within RPC methods to invoke another RPC method.
8
+
9
+ ## 8.8.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Deprecate `RpcContext.campaignKey`. The `getCampaignKey` RPC should be used instead.
14
+
15
+ To get the default campaign key, you can call the RPC method:
16
+
17
+ ```typescript
18
+ // Before
19
+ async function rpcMethod(context) {
20
+ const campaignKey = { context }
21
+ // ...
22
+ }
23
+ // After
24
+ async function rpcMethod(context) {
25
+ const campaignKey = await context.callRpc?.('getCampaignKey')
26
+ // ...
27
+ }
28
+ ```
29
+
30
+ To override the default campaign key, override the getCampaignKey RPC with your own implementation.
31
+ See [Overriding core RPC Methods](https://scayle.dev/en/storefront-guide/developer-guide/basic-setup/rpc-methods#overriding-core-rpc-methods) for more.
32
+
3
33
  ## 8.7.1
4
34
 
5
35
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as rpcMethods from './rpc/methods';
2
+ import type { RpcContext } from './types';
2
3
  export { rpcMethods };
3
4
  export * from './errors';
4
5
  export * from './utils';
@@ -65,4 +66,5 @@ export type RpcMethodParameters<N extends RpcMethodName> = Parameters<RpcMethod<
65
66
  * @template N The name of the RPC method.
66
67
  */
67
68
  export type RpcMethodReturnType<N extends RpcMethodName> = ReturnType<RpcMethod<N>>;
69
+ export type RpcMethodCall = <N extends RpcMethodName, P extends RpcMethodParameters<N>, TResult extends Exclude<Awaited<RpcMethodReturnType<N>>, Response>>(...args: P extends RpcContext ? [N] : [N, P]) => TResult;
68
70
  export { ExistingItemHandling, AddToBasketFailureKind, UpdateBasketItemFailureKind, AddToWishlistFailureKind, PromotionEffectType, FilterTypes, APISortOption, APISortOrder, type ProductSortConfig, } from '@scayle/storefront-api';
@@ -31,7 +31,8 @@ export const addItemToBasket = async function addItemToBasket2({
31
31
  "No Session found"
32
32
  );
33
33
  }
34
- const { campaignKey, sapiClient, basketKey } = context;
34
+ const { sapiClient, basketKey } = context;
35
+ const campaignKey = await context.callRpc?.("getCampaignKey");
35
36
  const resolvedWith = getWithParams(
36
37
  { with: _with },
37
38
  context
@@ -91,7 +92,8 @@ export const addItemsToBasket = async function addItemsToBasket2(params, context
91
92
  "No Session found"
92
93
  );
93
94
  }
94
- const { campaignKey, sapiClient, basketKey } = context;
95
+ const { sapiClient, basketKey } = context;
96
+ const campaignKey = await context.callRpc?.("getCampaignKey");
95
97
  const resolvedWith = getWithParams(params, context);
96
98
  const itemsToBeAddedOrUpdated = params.items.map((item) => ({
97
99
  variantId: item.variantId,
@@ -151,7 +153,8 @@ export const getBasket = async function getBasket2(options, context) {
151
153
  "No Session found"
152
154
  );
153
155
  }
154
- const { sapiClient, campaignKey, basketKey } = context;
156
+ const { sapiClient, basketKey } = context;
157
+ const campaignKey = await context.callRpc?.("getCampaignKey");
155
158
  const resolvedWith = getWithParams(
156
159
  { with: options },
157
160
  context
@@ -183,7 +186,8 @@ export const removeItemFromBasket = async function removeItemFromBasket2(options
183
186
  "No Session found"
184
187
  );
185
188
  }
186
- const { sapiClient, campaignKey, basketKey } = context;
189
+ const { sapiClient, basketKey } = context;
190
+ const campaignKey = await context.callRpc?.("getCampaignKey");
187
191
  const resolvedWith = getWithParams(options, context);
188
192
  const basket = await sapiClient.basket.deleteItem(
189
193
  basketKey,
@@ -0,0 +1,13 @@
1
+ import type { RpcContext } from '../../types';
2
+ /**
3
+ * RPC method to retrieve the current campaign key.
4
+ * Used by other RPC methods which depend on a campaign key.
5
+ * Will return the key for the earliest active campaign.
6
+ *
7
+ * Uses SAPI's list-campaigns endpoint
8
+ * @see https://scayle.dev/en/api-guides/storefront-api/resources/campaigns/list-campaigns
9
+ *
10
+ * @param context The RPC Context
11
+ * @returns The current active campaign key.
12
+ */
13
+ export declare const getCampaignKey: (context: RpcContext) => Promise<string | undefined>;
@@ -0,0 +1,23 @@
1
+ import {
2
+ campaignHasNotEnded,
3
+ sortCampaignsByDateAscending,
4
+ isCampaignActive
5
+ } from "../../utils/campaign.mjs";
6
+ export const getCampaignKey = async (context) => {
7
+ if (context.campaignKey) {
8
+ return context.campaignKey;
9
+ }
10
+ const { cached, sapiClient } = context;
11
+ const { campaigns } = await cached(async () => {
12
+ const { entities } = await sapiClient.campaigns.get();
13
+ return {
14
+ campaigns: entities.filter((campaign) => {
15
+ return campaignHasNotEnded(campaign);
16
+ }).sort(sortCampaignsByDateAscending)
17
+ };
18
+ }, {
19
+ cacheKeyPrefix: "get-campaignKey",
20
+ ttl: 5 * 60
21
+ })();
22
+ return campaigns.find(isCampaignActive)?.key;
23
+ };
@@ -1,5 +1,4 @@
1
- import { ErrorResponse } from '../../../errors';
2
- import type { RpcContext } from '../../../types';
1
+ import type { RpcHandler } from '../../../types';
3
2
  interface CheckoutJwtPayload {
4
3
  voucher?: string;
5
4
  customData?: Record<string, unknown>;
@@ -9,7 +8,7 @@ interface CheckoutJwtPayload {
9
8
  };
10
9
  carrier?: string;
11
10
  }
12
- export declare const getCheckoutToken: (jwtPayload: CheckoutJwtPayload | undefined, context: RpcContext) => Promise<ErrorResponse | {
11
+ export declare const getCheckoutToken: RpcHandler<CheckoutJwtPayload | undefined, {
13
12
  accessToken: string;
14
13
  checkoutJwt: string;
15
14
  }>;
@@ -28,14 +28,15 @@ export const getCheckoutToken = async function getCheckoutToken2(jwtPayload = {}
28
28
  const secret = new TextEncoder().encode(context.checkout.secret);
29
29
  const now = /* @__PURE__ */ new Date();
30
30
  const { voucher, customData, preferredCollectionPoint, carrier } = jwtPayload;
31
+ const campaignKey = await context.callRpc?.("getCampaignKey");
31
32
  const checkoutJwt = await new SignJWT({
32
33
  voucher,
33
34
  customData,
34
35
  preferredCollectionPoint,
35
36
  carrier,
36
37
  basketId: context.basketKey,
37
- campaignKey: context.campaignKey
38
- }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"8.7.1"}`).setProtectedHeader({ alg: "HS256", typ: "JWT" }).sign(secret);
38
+ campaignKey
39
+ }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"8.9.0"}`).setProtectedHeader({ alg: "HS256", typ: "JWT" }).sign(secret);
39
40
  return {
40
41
  accessToken: refreshedAccessToken,
41
42
  checkoutJwt
@@ -1,6 +1,7 @@
1
1
  export * from './basket/basket';
2
2
  export * from './brands';
3
3
  export * from './categories';
4
+ export { getCampaignKey } from './campaign';
4
5
  export * from './cbd';
5
6
  export { getProductById, getProductsByIds, getProductsByReferenceKeys, getProductsCount, fetchAllFiltersForCategory, getFilters, getProductsByCategory, } from './products';
6
7
  export * from './search';
@@ -1,6 +1,7 @@
1
1
  export * from "./basket/basket.mjs";
2
2
  export * from "./brands.mjs";
3
3
  export * from "./categories.mjs";
4
+ export { getCampaignKey } from "./campaign.mjs";
4
5
  export * from "./cbd.mjs";
5
6
  export {
6
7
  getProductById,
@@ -1,6 +1,4 @@
1
- import type { FiltersEndpointResponseData } from '@scayle/storefront-api';
2
- import type { FetchFiltersParams, FetchProductParams, FetchProductsByCategoryParams, FetchProductsByIdsParams, FetchProductsByReferenceKeysParams, FetchProductsCountParams, Product, RpcContext } from '../../types';
3
- import { ErrorResponse } from '../../errors';
1
+ import type { FetchFiltersParams, FetchFiltersResponse, FetchProductParams, FetchProductsByCategoryParams, FetchProductsByCategoryResponse, FetchProductsByIdsParams, FetchProductsByReferenceKeysParams, FetchProductsCountParams, FetchProductsCountResponse, Product, RpcContext, RpcHandler } from '../../types';
4
2
  /**
5
3
  * For functions which accept either a category path or category ID,
6
4
  * this function can resolve the ID which is necessary for the calling
@@ -20,32 +18,15 @@ export declare function resolveCategoryIdFromParams(context: RpcContext, params:
20
18
  category: string | undefined;
21
19
  categoryId: number | undefined;
22
20
  }>;
23
- export declare const getProductById: (options: FetchProductParams, { sapiClient, cached, campaignKey, withParams }: RpcContext) => Promise<Response | Product>;
24
- export declare const getProductsByIds: (options: FetchProductsByIdsParams, { sapiClient, cached, campaignKey, withParams }: RpcContext) => Promise<Response | Product[]>;
25
- export declare const getProductsByReferenceKeys: (options: FetchProductsByReferenceKeysParams, { sapiClient, cached, campaignKey, withParams }: RpcContext) => Promise<Response | Product[]>;
26
- export declare const getProductsCount: (params: FetchProductsCountParams, context: RpcContext) => Promise<(string[] & ErrorResponse) | {
27
- count: number;
28
- }>;
29
- export declare const fetchAllFiltersForCategory: ({ includedFilters, category }: {
21
+ export declare const getProductById: RpcHandler<FetchProductParams, Product>;
22
+ export declare const getProductsByIds: RpcHandler<FetchProductsByIdsParams, Product[]>;
23
+ export declare const getProductsByReferenceKeys: RpcHandler<FetchProductsByReferenceKeysParams, Product[]>;
24
+ export declare const getProductsCount: RpcHandler<FetchProductsCountParams, FetchProductsCountResponse>;
25
+ export declare const fetchAllFiltersForCategory: RpcHandler<{
30
26
  includedFilters?: string[];
31
27
  category: {
32
28
  id?: number;
33
29
  };
34
- }, { cached, sapiClient, campaignKey }: RpcContext) => Promise<string[]>;
35
- export declare const getFilters: (params: FetchFiltersParams, context: RpcContext) => Promise<(string[] & ErrorResponse) | {
36
- filters: FiltersEndpointResponseData;
37
- unfilteredCount: number;
38
- }>;
39
- export declare const getProductsByCategory: (params: FetchProductsByCategoryParams, context: RpcContext) => Promise<(string[] & ErrorResponse) | {
40
- products: Product[];
41
- pagination: {
42
- current: number;
43
- total: number;
44
- perPage: number;
45
- page: number;
46
- first: number;
47
- prev: number;
48
- next: number;
49
- last: number;
50
- };
51
- }>;
30
+ }, string[]>;
31
+ export declare const getFilters: RpcHandler<FetchFiltersParams, FetchFiltersResponse>;
32
+ export declare const getProductsByCategory: RpcHandler<FetchProductsByCategoryParams, FetchProductsByCategoryResponse>;
@@ -34,7 +34,9 @@ export async function resolveCategoryIdFromParams(context, params) {
34
34
  }
35
35
  return { category, categoryId };
36
36
  }
37
- export const getProductById = async function getProductById2(options, { sapiClient, cached, campaignKey, withParams }) {
37
+ export const getProductById = async function getProductById2(options, context) {
38
+ const { sapiClient, cached, withParams } = context;
39
+ const campaignKey = await context.callRpc?.("getCampaignKey");
38
40
  return await cached(
39
41
  mapSAPIFetchErrorToResponse(sapiClient.products.getById),
40
42
  {
@@ -48,7 +50,9 @@ export const getProductById = async function getProductById2(options, { sapiClie
48
50
  includeSellableForFree: options.includeSellableForFree
49
51
  });
50
52
  };
51
- export const getProductsByIds = async function getProductsByIds2(options, { sapiClient, cached, campaignKey, withParams }) {
53
+ export const getProductsByIds = async function getProductsByIds2(options, context) {
54
+ const { sapiClient, cached, withParams } = context;
55
+ const campaignKey = await context.callRpc?.("getCampaignKey");
52
56
  return await cached(
53
57
  mapSAPIFetchErrorToResponse(sapiClient.products.getByIds),
54
58
  {
@@ -61,7 +65,9 @@ export const getProductsByIds = async function getProductsByIds2(options, { sapi
61
65
  pricePromotionKey: options.pricePromotionKey
62
66
  });
63
67
  };
64
- export const getProductsByReferenceKeys = async function getProductsByReferenceKeys2(options, { sapiClient, cached, campaignKey, withParams }) {
68
+ export const getProductsByReferenceKeys = async function getProductsByReferenceKeys2(options, context) {
69
+ const { sapiClient, cached, withParams } = context;
70
+ const campaignKey = await context.callRpc?.("getCampaignKey");
65
71
  return await cached(
66
72
  mapSAPIFetchErrorToResponse(sapiClient.products.getByReferenceKeys),
67
73
  {
@@ -82,7 +88,8 @@ export const getProductsCount = async function getProductsCount2(params, context
82
88
  includeSellableForFree = false,
83
89
  orFiltersOperator = void 0
84
90
  } = params;
85
- const { cached, sapiClient, campaignKey } = context;
91
+ const { cached, sapiClient } = context;
92
+ const campaignKey = await context.callRpc?.("getCampaignKey");
86
93
  const { categoryId } = await resolveCategoryIdFromParams(context, params);
87
94
  const response = await getSanitizedAttributes(
88
95
  context,
@@ -121,7 +128,9 @@ export const getProductsCount = async function getProductsCount2(params, context
121
128
  count: productCount?.pagination.total
122
129
  };
123
130
  };
124
- export const fetchAllFiltersForCategory = async function fetchAllFiltersForCategory2({ includedFilters = [], category }, { cached, sapiClient, campaignKey }) {
131
+ export const fetchAllFiltersForCategory = async function fetchAllFiltersForCategory2({ includedFilters = [], category }, context) {
132
+ const { cached, sapiClient } = context;
133
+ const campaignKey = await context.callRpc?.("getCampaignKey");
125
134
  const fetchAndMapFiltersToSlugs = async () => {
126
135
  const filtersFromSAPI = await cached(sapiClient.filters.get)({
127
136
  where: { categoryId: category.id },
@@ -162,7 +171,8 @@ export const getFilters = async function getFilters2(params, context) {
162
171
  includeSellableForFree = false,
163
172
  orFiltersOperator
164
173
  } = params;
165
- const { cached, sapiClient, campaignKey } = context;
174
+ const { cached, sapiClient } = context;
175
+ const campaignKey = await context.callRpc?.("getCampaignKey");
166
176
  const { category, categoryId } = await resolveCategoryIdFromParams(
167
177
  context,
168
178
  params
@@ -241,7 +251,8 @@ export const getProductsByCategory = async function getProductsByCategory2(param
241
251
  orFiltersOperator = void 0,
242
252
  trackSearchAnalyticsEvent = void 0
243
253
  } = params;
244
- const { cached, sapiClient, campaignKey } = context;
254
+ const { cached, sapiClient } = context;
255
+ const campaignKey = await context.callRpc?.("getCampaignKey");
245
256
  const { category, categoryId } = await resolveCategoryIdFromParams(
246
257
  context,
247
258
  params
@@ -1,8 +1,8 @@
1
1
  import type { ProductWith, VariantDetail } from '@scayle/storefront-api';
2
- import type { RpcContext } from '../../types';
2
+ import type { RpcHandler } from '../../types';
3
3
  export interface FetchVariantsParams {
4
4
  ids: number[];
5
5
  include?: ProductWith;
6
6
  pricePromotionKey?: string;
7
7
  }
8
- export declare const getVariantById: ({ ids, include, pricePromotionKey }: FetchVariantsParams, { sapiClient, cached, campaignKey, withParams }: RpcContext) => Promise<Response | VariantDetail[]>;
8
+ export declare const getVariantById: RpcHandler<FetchVariantsParams, VariantDetail[]>;
@@ -2,7 +2,9 @@ import { mapSAPIFetchErrorToResponse } from "../../utils/sapi.mjs";
2
2
  const defaultWith = {
3
3
  attributes: "all"
4
4
  };
5
- export const getVariantById = async function getVariantById2({ ids, include = defaultWith, pricePromotionKey = "default" }, { sapiClient, cached, campaignKey, withParams }) {
5
+ export const getVariantById = async function getVariantById2({ ids, include = defaultWith, pricePromotionKey = "default" }, context) {
6
+ const { sapiClient, cached, withParams } = context;
7
+ const campaignKey = await context.callRpc?.("getCampaignKey");
6
8
  const resolvedWith = include ?? withParams?.variant ?? defaultWith;
7
9
  return await cached(
8
10
  mapSAPIFetchErrorToResponse(sapiClient.variants.getByIds),
@@ -1,13 +1,13 @@
1
1
  import type { Wishlist } from '@scayle/storefront-api';
2
- import type { RpcContext, WishlistWithOptions } from '../../types';
3
- export declare const getWishlist: (options: WishlistWithOptions | undefined, context: RpcContext) => Promise<Response | Wishlist>;
4
- export declare const addItemToWishlist: (options: {
2
+ import type { ParamRpcHandler, RpcHandler, WishlistWithOptions } from '../../types';
3
+ export declare const getWishlist: RpcHandler<WishlistWithOptions | undefined, Wishlist>;
4
+ export declare const addItemToWishlist: ParamRpcHandler<{
5
5
  variantId?: number;
6
6
  productId?: number;
7
7
  with?: WishlistWithOptions;
8
- }, context: RpcContext) => Promise<Wishlist | Response>;
9
- export declare const removeItemFromWishlist: (options: {
8
+ }, Wishlist | Response>;
9
+ export declare const removeItemFromWishlist: RpcHandler<{
10
10
  itemKey: string;
11
11
  with?: WishlistWithOptions;
12
- }, context: RpcContext) => Promise<Response | Wishlist>;
13
- export declare const clearWishlist: (context: RpcContext) => Promise<Response | Wishlist>;
12
+ }, Wishlist>;
13
+ export declare const clearWishlist: RpcHandler<Wishlist>;
@@ -18,7 +18,8 @@ export const getWishlist = async function getWishlist2(options, context) {
18
18
  "No Session found"
19
19
  );
20
20
  }
21
- const { sapiClient, campaignKey, wishlistKey } = context;
21
+ const { sapiClient, wishlistKey } = context;
22
+ const campaignKey = await context.callRpc?.("getCampaignKey");
22
23
  const resolvedWith = getWithParams({ with: options }, context);
23
24
  return await mapSAPIFetchErrorToResponse(sapiClient.wishlist.get)(
24
25
  wishlistKey,
@@ -37,7 +38,8 @@ export const addItemToWishlist = async function addItemToWishlist2(options, cont
37
38
  "No Session found"
38
39
  );
39
40
  }
40
- const { sapiClient, campaignKey, wishlistKey } = context;
41
+ const { sapiClient, wishlistKey } = context;
42
+ const campaignKey = await context.callRpc?.("getCampaignKey");
41
43
  const { productId, variantId } = options;
42
44
  const resolvedWith = getWithParams(options, context);
43
45
  const pricePromotionKey = resolvedWith?.pricePromotionKey ?? "";
@@ -73,7 +75,8 @@ export const removeItemFromWishlist = async function removeItemFromWishlist2(opt
73
75
  "No Session found"
74
76
  );
75
77
  }
76
- const { sapiClient, campaignKey, wishlistKey } = context;
78
+ const { sapiClient, wishlistKey } = context;
79
+ const campaignKey = await context.callRpc?.("getCampaignKey");
77
80
  const resolvedWith = getWithParams(options, context);
78
81
  return await mapSAPIFetchErrorToResponse(sapiClient.wishlist.deleteItem)(
79
82
  wishlistKey,
@@ -5,6 +5,7 @@ import type { CachedType } from '../../cache/cached';
5
5
  import type { AuthenticationType, ShopUser } from '../user';
6
6
  import type { BasketWithOptions, CategoryWith, ProductCategoryWith, ProductWith, SearchV2With, SearchWith, VariantWith, WishlistWithOptions } from '../';
7
7
  import type { IDPConfig, OAuthTokens } from './auth';
8
+ import type { RpcMethodCall } from '../..';
8
9
  export type WithParams = Partial<{
9
10
  basket: BasketWithOptions;
10
11
  wishlist: WishlistWithOptions;
@@ -77,6 +78,27 @@ export type RpcContext = {
77
78
  shopId: number;
78
79
  domain?: string;
79
80
  withParams?: WithParams;
81
+ /**
82
+ * @deprecated The `getCampaignKey` RPC method should be used instead to retrieve the default campaign key.
83
+ *
84
+ * @example
85
+ *
86
+ * ```typescript
87
+ * // Before
88
+ * async function rpcMethod(context) {
89
+ * const campaignKey = { context }
90
+ * // ...
91
+ * }
92
+ * // After
93
+ * async function rpcMethod(context) {
94
+ * const campaignKey = await context.callRpc?.('getCampaignKey')
95
+ * // ...
96
+ * }
97
+ * ```
98
+ *
99
+ * To override the default campaign key, override the getCampaignKey RPC with your own implementation
100
+ * @see https://scayle.dev/en/storefront-guide/developer-guide/basic-setup/rpc-methods#overriding-core-rpc-methods
101
+ */
80
102
  campaignKey?: string;
81
103
  destroySessionsForUserId: (userId: number, sessionsToKeep?: string[]) => Promise<void>;
82
104
  generateBasketKeyForUserId: (userId: string) => Promise<string>;
@@ -99,6 +121,7 @@ export type RpcContext = {
99
121
  callHook?: Hookable<StorefrontHooks>['callHook'];
100
122
  callHookParallel?: Hookable<StorefrontHooks>['callHookParallel'];
101
123
  callHookWith?: Hookable<StorefrontHooks>['callHookWith'];
124
+ callRpc?: RpcMethodCall;
102
125
  } & AdditionalRpcContext & (ContextWithoutSession | ContextWithSession);
103
126
  export type RpcContextWithSession = RpcContext & ContextWithSession;
104
127
  export declare function assertSession(context: RpcContext): asserts context is RpcContextWithSession;
@@ -0,0 +1,38 @@
1
+ import type { Campaign } from '@scayle/storefront-api';
2
+ /**
3
+ * Checks if a given campaign is currently active.
4
+ *
5
+ * Determines if the current time falls within the campaign's start and end dates.
6
+ *
7
+ * @see https://scayle.dev/en/user-guide/shops/promotions/discounts-and-offers/price-campaigns
8
+ *
9
+ * @param campaign The campaign to check.
10
+ *
11
+ * @returns True if the campaign is active, false otherwise.
12
+ */
13
+ export declare const isCampaignActive: (campaign?: Campaign) => boolean;
14
+ /**
15
+ * Checks if a given campaign has not yet ended.
16
+ *
17
+ * Compares the campaign's end date with the current time.
18
+ *
19
+ * @see https://scayle.dev/en/user-guide/shops/promotions/discounts-and-offers/price-campaigns
20
+ *
21
+ * @param campaign The campaign to check.
22
+ *
23
+ * @returns True if the campaign has not ended, false otherwise.
24
+ */
25
+ export declare const campaignHasNotEnded: (campaign: Campaign) => boolean;
26
+ /**
27
+ * Sorts campaigns by their start date in ascending order.
28
+ *
29
+ * Uses the difference between start times to determine order.
30
+ *
31
+ * @see https://scayle.dev/en/user-guide/shops/promotions/discounts-and-offers/price-campaigns
32
+ *
33
+ * @param a The first campaign to compare.
34
+ * @param b The second campaign to compare.
35
+ *
36
+ * @returns A negative number if `a` starts before `b`, a positive number if `a` starts after `b`, and 0 if they start at the same time.
37
+ */
38
+ export declare const sortCampaignsByDateAscending: (a: Campaign, b: Campaign) => number;
@@ -0,0 +1,17 @@
1
+ export const isCampaignActive = (campaign) => {
2
+ if (!campaign) {
3
+ return false;
4
+ }
5
+ const timeNow = Date.now();
6
+ const campaignStart = new Date(campaign.start_at).getTime();
7
+ const hasStarted = timeNow >= campaignStart;
8
+ const campaignEnd = new Date(campaign.end_at).getTime();
9
+ const hasEnded = timeNow >= campaignEnd;
10
+ return hasStarted && !hasEnded;
11
+ };
12
+ export const campaignHasNotEnded = (campaign) => {
13
+ return new Date(campaign.end_at).getTime() > Date.now();
14
+ };
15
+ export const sortCampaignsByDateAscending = (a, b) => {
16
+ return new Date(a.start_at).getTime() - new Date(b.start_at).getTime();
17
+ };
@@ -2,3 +2,4 @@ export * from './timeout';
2
2
  export * from './log';
3
3
  export * from './fetch';
4
4
  export * from './basket';
5
+ export * from './campaign';
@@ -2,3 +2,4 @@ export * from "./timeout.mjs";
2
2
  export * from "./log.mjs";
3
3
  export * from "./fetch.mjs";
4
4
  export * from "./basket.mjs";
5
+ export * from "./campaign.mjs";
@@ -1,6 +1,8 @@
1
1
  import { ExistingItemHandling } from "@scayle/storefront-api";
2
+ import { getCampaignKey } from "../rpc/methods/index.mjs";
2
3
  export const mergeBaskets = async (fromBasketKey, toBasketKey, withOptions, context) => {
3
- const { sapiClient, campaignKey } = context;
4
+ const { sapiClient } = context;
5
+ const campaignKey = await getCampaignKey(context);
4
6
  const fromOriginBasket = await sapiClient.basket.get(fromBasketKey, {
5
7
  with: withOptions,
6
8
  campaignKey,
@@ -43,7 +45,8 @@ export const mergeBaskets = async (fromBasketKey, toBasketKey, withOptions, cont
43
45
  }
44
46
  };
45
47
  export const mergeWishlists = async (fromWishlistKey, toWishlistKey, withOptions, context) => {
46
- const { sapiClient, campaignKey } = context;
48
+ const { sapiClient } = context;
49
+ const campaignKey = await getCampaignKey(context);
47
50
  const oldWishlist = await sapiClient.wishlist.get(fromWishlistKey, {
48
51
  with: withOptions,
49
52
  campaignKey
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scayle/storefront-core",
3
- "version": "8.7.1",
3
+ "version": "8.9.0",
4
4
  "description": "Collection of essential utilities to work with the Storefront API",
5
5
  "author": "SCAYLE Commerce Engine",
6
6
  "license": "MIT",
@@ -68,17 +68,17 @@
68
68
  "devDependencies": {
69
69
  "@scayle/eslint-config-storefront": "4.4.1",
70
70
  "@types/crypto-js": "4.2.2",
71
- "@types/node": "22.13.4",
71
+ "@types/node": "22.13.5",
72
72
  "@types/webpack-env": "1.18.8",
73
73
  "@vitest/coverage-v8": "2.1.9",
74
74
  "dprint": "0.49.0",
75
- "eslint": "9.20.1",
75
+ "eslint": "9.21.0",
76
76
  "eslint-formatter-gitlab": "5.1.0",
77
77
  "publint": "0.2.12",
78
78
  "rimraf": "6.0.1",
79
79
  "typescript": "5.7.3",
80
80
  "unbuild": "3.3.1",
81
- "unstorage": "1.14.4",
81
+ "unstorage": "1.15.0",
82
82
  "vitest": "2.1.9"
83
83
  }
84
84
  }