@scayle/storefront-core 8.7.0 → 8.8.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/api/customer.d.ts +73 -0
  3. package/dist/api/customer.mjs +59 -0
  4. package/dist/api/oauth.d.ts +62 -12
  5. package/dist/api/oauth.mjs +51 -15
  6. package/dist/cache/cache.d.ts +47 -0
  7. package/dist/cache/cached.d.ts +93 -0
  8. package/dist/cache/cached.mjs +62 -0
  9. package/dist/cache/providers/unstorage.d.ts +64 -0
  10. package/dist/cache/providers/unstorage.mjs +64 -0
  11. package/dist/constants/cache.d.ts +4 -0
  12. package/dist/constants/hash.d.ts +4 -0
  13. package/dist/constants/hash.mjs +1 -0
  14. package/dist/constants/httpStatus.d.ts +12 -2
  15. package/dist/constants/httpStatus.mjs +2 -2
  16. package/dist/constants/product.d.ts +3 -0
  17. package/dist/constants/promotion.d.ts +6 -0
  18. package/dist/constants/sorting.d.ts +6 -0
  19. package/dist/constants/withParameters.d.ts +28 -0
  20. package/dist/errors/errorResponse.d.ts +37 -0
  21. package/dist/errors/errorResponse.mjs +14 -0
  22. package/dist/helpers/filterHelper.mjs +1 -1
  23. package/dist/index.d.ts +48 -0
  24. package/dist/rpc/methods/basket/basket.mjs +9 -4
  25. package/dist/rpc/methods/campaign.d.ts +13 -0
  26. package/dist/rpc/methods/campaign.mjs +23 -0
  27. package/dist/rpc/methods/checkout/checkout.mjs +4 -2
  28. package/dist/rpc/methods/index.d.ts +1 -0
  29. package/dist/rpc/methods/index.mjs +1 -0
  30. package/dist/rpc/methods/products.d.ts +4 -4
  31. package/dist/rpc/methods/products.mjs +19 -7
  32. package/dist/rpc/methods/variants.d.ts +1 -1
  33. package/dist/rpc/methods/variants.mjs +4 -1
  34. package/dist/rpc/methods/wishlist.mjs +7 -3
  35. package/dist/types/api/context.d.ts +3 -0
  36. package/dist/utils/basket.d.ts +23 -0
  37. package/dist/utils/campaign.d.ts +23 -0
  38. package/dist/utils/campaign.mjs +17 -0
  39. package/dist/utils/fetch.d.ts +21 -2
  40. package/dist/utils/hash.d.ts +67 -0
  41. package/dist/utils/hash.mjs +3 -3
  42. package/dist/utils/index.d.ts +1 -0
  43. package/dist/utils/index.mjs +1 -0
  44. package/dist/utils/keys.d.ts +38 -0
  45. package/dist/utils/log.d.ts +77 -8
  46. package/dist/utils/log.mjs +61 -6
  47. package/dist/utils/response.d.ts +8 -3
  48. package/dist/utils/sapi.d.ts +10 -2
  49. package/dist/utils/timeout.d.ts +18 -0
  50. package/dist/utils/user.d.ts +22 -0
  51. package/dist/utils/user.mjs +5 -2
  52. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -6,15 +6,63 @@ export * from './constants';
6
6
  export * from './cache';
7
7
  export * from './helpers';
8
8
  export * from './types';
9
+ /**
10
+ * Core RPC methods available within `@scayle/storefront-core`.
11
+ */
9
12
  type RpcMethodsCore = typeof rpcMethods;
13
+ /**
14
+ * Interface for extending RPC methods within `@scayle/storefront-core`. Allows custom RPC methods
15
+ * to be added to the SCAYLE Storefront application by extending this interface.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // nuxt.config.ts
20
+ * import * as customRpcMethods from './rpc'
21
+ * declare module '@scayle/storefront-core' {
22
+ * export interface RpcMethodsStorefrontInternal extends typeof customRpcMethods {}
23
+ * }
24
+ * ```
25
+ */
10
26
  export interface RpcMethodsStorefrontInternal {
11
27
  }
28
+ /**
29
+ * Combined RPC methods, merging core methods with SCAYLE Storefront application extensions.
30
+ * Extensions from `@scayle/storefront-core` take precedence in case of naming conflicts.
31
+ */
12
32
  export type RpcMethods = {
13
33
  [Key in keyof RpcMethodsCore | keyof RpcMethodsStorefrontInternal]: Key extends keyof RpcMethodsStorefrontInternal ? RpcMethodsStorefrontInternal[Key] : Key extends keyof RpcMethodsCore ? RpcMethodsCore[Key] : never;
14
34
  };
35
+ /**
36
+ * Type alias representing the name of an RPC method. This uses a
37
+ * resolution technique to avoid inlining method names in compiled
38
+ * type declarations, which is essential for supporting dynamically
39
+ * added RPC methods from the SCAYLE Storefront application.
40
+ *
41
+ * @see https://github.com/microsoft/TypeScript/pull/42149#issuecomment-865385560
42
+ * @
43
+ * Without this workaround, the signature to `rpcCall` looks something like
44
+ * `rpcCall(m: 'addItemToBasket' | 'addItemToWishlist'...)`.
45
+ * But we need it to look like `rpcCall(m: RpcMethodName)` in order to include
46
+ * RPC methods added by the SCAYLE Storefront application.
47
+ */
15
48
  type Resolve<T> = T extends T ? T : never;
16
49
  export type RpcMethodName = Resolve<keyof RpcMethods>;
50
+ /**
51
+ * Describes the type of a specific RPC method by its name.
52
+ *
53
+ * @template N The name of the RPC method.
54
+ */
17
55
  export type RpcMethod<N extends RpcMethodName> = RpcMethods[N];
56
+ /**
57
+ * Describes the type of the parameters for a specific RPC method.
58
+ *
59
+ * @template N The name of the RPC method.
60
+ */
18
61
  export type RpcMethodParameters<N extends RpcMethodName> = Parameters<RpcMethod<N>>[0];
62
+ /**
63
+ * Describes the return type of a specific RPC method.
64
+ *
65
+ * @template N The name of the RPC method.
66
+ */
19
67
  export type RpcMethodReturnType<N extends RpcMethodName> = ReturnType<RpcMethod<N>>;
20
68
  export { ExistingItemHandling, AddToBasketFailureKind, UpdateBasketItemFailureKind, AddToWishlistFailureKind, PromotionEffectType, FilterTypes, APISortOption, APISortOrder, type ProductSortConfig, } from '@scayle/storefront-api';
@@ -9,6 +9,7 @@ import {
9
9
  import { mergeBaskets as mergeBasketFunction } from "../../../utils/user.mjs";
10
10
  import { unwrap } from "../../../utils/response.mjs";
11
11
  import { wasAddedWithReducedQuantity } from "../../../utils/index.mjs";
12
+ import { getCampaignKey } from "../campaign.mjs";
12
13
  const SAPI_ERROR_NAME = "SAPI ERROR";
13
14
  function getWithParams(params, context) {
14
15
  return params.with ?? context.withParams?.basket ?? MIN_WITH_PARAMS_BASKET;
@@ -31,7 +32,8 @@ export const addItemToBasket = async function addItemToBasket2({
31
32
  "No Session found"
32
33
  );
33
34
  }
34
- const { campaignKey, sapiClient, basketKey } = context;
35
+ const { sapiClient, basketKey } = context;
36
+ const campaignKey = await getCampaignKey(context);
35
37
  const resolvedWith = getWithParams(
36
38
  { with: _with },
37
39
  context
@@ -91,7 +93,8 @@ export const addItemsToBasket = async function addItemsToBasket2(params, context
91
93
  "No Session found"
92
94
  );
93
95
  }
94
- const { campaignKey, sapiClient, basketKey } = context;
96
+ const { sapiClient, basketKey } = context;
97
+ const campaignKey = await getCampaignKey(context);
95
98
  const resolvedWith = getWithParams(params, context);
96
99
  const itemsToBeAddedOrUpdated = params.items.map((item) => ({
97
100
  variantId: item.variantId,
@@ -151,7 +154,8 @@ export const getBasket = async function getBasket2(options, context) {
151
154
  "No Session found"
152
155
  );
153
156
  }
154
- const { sapiClient, campaignKey, basketKey } = context;
157
+ const { sapiClient, basketKey } = context;
158
+ const campaignKey = await getCampaignKey(context);
155
159
  const resolvedWith = getWithParams(
156
160
  { with: options },
157
161
  context
@@ -183,7 +187,8 @@ export const removeItemFromBasket = async function removeItemFromBasket2(options
183
187
  "No Session found"
184
188
  );
185
189
  }
186
- const { sapiClient, campaignKey, basketKey } = context;
190
+ const { sapiClient, basketKey } = context;
191
+ const campaignKey = await getCampaignKey(context);
187
192
  const resolvedWith = getWithParams(options, context);
188
193
  const basket = await sapiClient.basket.deleteItem(
189
194
  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
+ };
@@ -3,6 +3,7 @@ import { hasSession } from "../../../types/index.mjs";
3
3
  import { ErrorResponse } from "../../../errors/index.mjs";
4
4
  import { HttpStatusCode, HttpStatusMessage } from "../../../constants/index.mjs";
5
5
  import { getAccessToken } from "../user.mjs";
6
+ import { getCampaignKey } from "../campaign.mjs";
6
7
  export const getCheckoutToken = async function getCheckoutToken2(jwtPayload = {}, context) {
7
8
  if (!hasSession(context)) {
8
9
  return new ErrorResponse(
@@ -28,14 +29,15 @@ export const getCheckoutToken = async function getCheckoutToken2(jwtPayload = {}
28
29
  const secret = new TextEncoder().encode(context.checkout.secret);
29
30
  const now = /* @__PURE__ */ new Date();
30
31
  const { voucher, customData, preferredCollectionPoint, carrier } = jwtPayload;
32
+ const campaignKey = await getCampaignKey(context);
31
33
  const checkoutJwt = await new SignJWT({
32
34
  voucher,
33
35
  customData,
34
36
  preferredCollectionPoint,
35
37
  carrier,
36
38
  basketId: context.basketKey,
37
- campaignKey: context.campaignKey
38
- }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"8.7.0"}`).setProtectedHeader({ alg: "HS256", typ: "JWT" }).sign(secret);
39
+ campaignKey
40
+ }).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"8.8.0"}`).setProtectedHeader({ alg: "HS256", typ: "JWT" }).sign(secret);
39
41
  return {
40
42
  accessToken: refreshedAccessToken,
41
43
  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,
@@ -20,9 +20,9 @@ export declare function resolveCategoryIdFromParams(context: RpcContext, params:
20
20
  category: string | undefined;
21
21
  categoryId: number | undefined;
22
22
  }>;
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[]>;
23
+ export declare const getProductById: (options: FetchProductParams, context: RpcContext) => Promise<Response | Product>;
24
+ export declare const getProductsByIds: (options: FetchProductsByIdsParams, context: RpcContext) => Promise<Response | Product[]>;
25
+ export declare const getProductsByReferenceKeys: (options: FetchProductsByReferenceKeysParams, context: RpcContext) => Promise<Response | Product[]>;
26
26
  export declare const getProductsCount: (params: FetchProductsCountParams, context: RpcContext) => Promise<(string[] & ErrorResponse) | {
27
27
  count: number;
28
28
  }>;
@@ -31,7 +31,7 @@ export declare const fetchAllFiltersForCategory: ({ includedFilters, category }:
31
31
  category: {
32
32
  id?: number;
33
33
  };
34
- }, { cached, sapiClient, campaignKey }: RpcContext) => Promise<string[]>;
34
+ }, context: RpcContext) => Promise<string[]>;
35
35
  export declare const getFilters: (params: FetchFiltersParams, context: RpcContext) => Promise<(string[] & ErrorResponse) | {
36
36
  filters: FiltersEndpointResponseData;
37
37
  unfilteredCount: number;
@@ -4,6 +4,7 @@ import { MINUTE } from "../../cache/index.mjs";
4
4
  import { unwrap } from "../../utils/response.mjs";
5
5
  import { mapSAPIFetchErrorToResponse } from "../../utils/sapi.mjs";
6
6
  import { ErrorResponse } from "../../errors/index.mjs";
7
+ import { getCampaignKey } from "./campaign.mjs";
7
8
  const MAX_PER_PAGE = 100;
8
9
  const sanitizeAttributesForSAPI = ({
9
10
  attributes,
@@ -34,7 +35,9 @@ export async function resolveCategoryIdFromParams(context, params) {
34
35
  }
35
36
  return { category, categoryId };
36
37
  }
37
- export const getProductById = async function getProductById2(options, { sapiClient, cached, campaignKey, withParams }) {
38
+ export const getProductById = async function getProductById2(options, context) {
39
+ const { sapiClient, cached, withParams } = context;
40
+ const campaignKey = await getCampaignKey(context);
38
41
  return await cached(
39
42
  mapSAPIFetchErrorToResponse(sapiClient.products.getById),
40
43
  {
@@ -48,7 +51,9 @@ export const getProductById = async function getProductById2(options, { sapiClie
48
51
  includeSellableForFree: options.includeSellableForFree
49
52
  });
50
53
  };
51
- export const getProductsByIds = async function getProductsByIds2(options, { sapiClient, cached, campaignKey, withParams }) {
54
+ export const getProductsByIds = async function getProductsByIds2(options, context) {
55
+ const { sapiClient, cached, withParams } = context;
56
+ const campaignKey = await getCampaignKey(context);
52
57
  return await cached(
53
58
  mapSAPIFetchErrorToResponse(sapiClient.products.getByIds),
54
59
  {
@@ -61,7 +66,9 @@ export const getProductsByIds = async function getProductsByIds2(options, { sapi
61
66
  pricePromotionKey: options.pricePromotionKey
62
67
  });
63
68
  };
64
- export const getProductsByReferenceKeys = async function getProductsByReferenceKeys2(options, { sapiClient, cached, campaignKey, withParams }) {
69
+ export const getProductsByReferenceKeys = async function getProductsByReferenceKeys2(options, context) {
70
+ const { sapiClient, cached, withParams } = context;
71
+ const campaignKey = await getCampaignKey(context);
65
72
  return await cached(
66
73
  mapSAPIFetchErrorToResponse(sapiClient.products.getByReferenceKeys),
67
74
  {
@@ -82,7 +89,8 @@ export const getProductsCount = async function getProductsCount2(params, context
82
89
  includeSellableForFree = false,
83
90
  orFiltersOperator = void 0
84
91
  } = params;
85
- const { cached, sapiClient, campaignKey } = context;
92
+ const { cached, sapiClient } = context;
93
+ const campaignKey = await getCampaignKey(context);
86
94
  const { categoryId } = await resolveCategoryIdFromParams(context, params);
87
95
  const response = await getSanitizedAttributes(
88
96
  context,
@@ -121,7 +129,9 @@ export const getProductsCount = async function getProductsCount2(params, context
121
129
  count: productCount?.pagination.total
122
130
  };
123
131
  };
124
- export const fetchAllFiltersForCategory = async function fetchAllFiltersForCategory2({ includedFilters = [], category }, { cached, sapiClient, campaignKey }) {
132
+ export const fetchAllFiltersForCategory = async function fetchAllFiltersForCategory2({ includedFilters = [], category }, context) {
133
+ const { cached, sapiClient } = context;
134
+ const campaignKey = await getCampaignKey(context);
125
135
  const fetchAndMapFiltersToSlugs = async () => {
126
136
  const filtersFromSAPI = await cached(sapiClient.filters.get)({
127
137
  where: { categoryId: category.id },
@@ -162,7 +172,8 @@ export const getFilters = async function getFilters2(params, context) {
162
172
  includeSellableForFree = false,
163
173
  orFiltersOperator
164
174
  } = params;
165
- const { cached, sapiClient, campaignKey } = context;
175
+ const { cached, sapiClient } = context;
176
+ const campaignKey = await getCampaignKey(context);
166
177
  const { category, categoryId } = await resolveCategoryIdFromParams(
167
178
  context,
168
179
  params
@@ -241,7 +252,8 @@ export const getProductsByCategory = async function getProductsByCategory2(param
241
252
  orFiltersOperator = void 0,
242
253
  trackSearchAnalyticsEvent = void 0
243
254
  } = params;
244
- const { cached, sapiClient, campaignKey } = context;
255
+ const { cached, sapiClient } = context;
256
+ const campaignKey = await getCampaignKey(context);
245
257
  const { category, categoryId } = await resolveCategoryIdFromParams(
246
258
  context,
247
259
  params
@@ -5,4 +5,4 @@ export interface FetchVariantsParams {
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: ({ ids, include, pricePromotionKey }: FetchVariantsParams, context: RpcContext) => Promise<Response | VariantDetail[]>;
@@ -1,8 +1,11 @@
1
1
  import { mapSAPIFetchErrorToResponse } from "../../utils/sapi.mjs";
2
+ import { getCampaignKey } from "./campaign.mjs";
2
3
  const defaultWith = {
3
4
  attributes: "all"
4
5
  };
5
- export const getVariantById = async function getVariantById2({ ids, include = defaultWith, pricePromotionKey = "default" }, { sapiClient, cached, campaignKey, withParams }) {
6
+ export const getVariantById = async function getVariantById2({ ids, include = defaultWith, pricePromotionKey = "default" }, context) {
7
+ const { sapiClient, cached, withParams } = context;
8
+ const campaignKey = await getCampaignKey(context);
6
9
  const resolvedWith = include ?? withParams?.variant ?? defaultWith;
7
10
  return await cached(
8
11
  mapSAPIFetchErrorToResponse(sapiClient.variants.getByIds),
@@ -7,6 +7,7 @@ import {
7
7
  import { unwrap } from "../../utils/response.mjs";
8
8
  import { ErrorResponse } from "../../errors/index.mjs";
9
9
  import { mapSAPIFetchErrorToResponse } from "../../utils/sapi.mjs";
10
+ import { getCampaignKey } from "./campaign.mjs";
10
11
  function getWithParams(params, context) {
11
12
  return params.with ?? context.withParams?.wishlist ?? MIN_WITH_PARAMS_WISHLIST;
12
13
  }
@@ -18,7 +19,8 @@ export const getWishlist = async function getWishlist2(options, context) {
18
19
  "No Session found"
19
20
  );
20
21
  }
21
- const { sapiClient, campaignKey, wishlistKey } = context;
22
+ const { sapiClient, wishlistKey } = context;
23
+ const campaignKey = await getCampaignKey(context);
22
24
  const resolvedWith = getWithParams({ with: options }, context);
23
25
  return await mapSAPIFetchErrorToResponse(sapiClient.wishlist.get)(
24
26
  wishlistKey,
@@ -37,7 +39,8 @@ export const addItemToWishlist = async function addItemToWishlist2(options, cont
37
39
  "No Session found"
38
40
  );
39
41
  }
40
- const { sapiClient, campaignKey, wishlistKey } = context;
42
+ const { sapiClient, wishlistKey } = context;
43
+ const campaignKey = await getCampaignKey(context);
41
44
  const { productId, variantId } = options;
42
45
  const resolvedWith = getWithParams(options, context);
43
46
  const pricePromotionKey = resolvedWith?.pricePromotionKey ?? "";
@@ -73,7 +76,8 @@ export const removeItemFromWishlist = async function removeItemFromWishlist2(opt
73
76
  "No Session found"
74
77
  );
75
78
  }
76
- const { sapiClient, campaignKey, wishlistKey } = context;
79
+ const { sapiClient, wishlistKey } = context;
80
+ const campaignKey = await getCampaignKey(context);
77
81
  const resolvedWith = getWithParams(options, context);
78
82
  return await mapSAPIFetchErrorToResponse(sapiClient.wishlist.deleteItem)(
79
83
  wishlistKey,
@@ -77,6 +77,9 @@ export type RpcContext = {
77
77
  shopId: number;
78
78
  domain?: string;
79
79
  withParams?: WithParams;
80
+ /**
81
+ * @deprecated Use the `getCampaignKey` RPC method instead.
82
+ */
80
83
  campaignKey?: string;
81
84
  destroySessionsForUserId: (userId: number, sessionsToKeep?: string[]) => Promise<void>;
82
85
  generateBasketKeyForUserId: (userId: string) => Promise<string>;
@@ -1,2 +1,25 @@
1
1
  import type { AddOrUpdateItemError } from '@scayle/storefront-api';
2
+ /**
3
+ * Checks if all basket add/update errors indicate that items were added with reduced quantity.
4
+ * This function helps determine if a basket operation partially succeeded, meaning some items were added but with a lower quantity than requested.
5
+ *
6
+ * @param errors An array of `AddOrUpdateItemError` objects, typically returned from a basket add/update operation.
7
+ * @returns `true` if all errors indicate reduced quantity, `false` otherwise (including when the `errors` array is empty, null, or undefined).
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const errors: AddOrUpdateItemError[] = [
12
+ * { operation: 'add', kind: AddToBasketFailureKind.ITEM_ADDED_WITH_REDUCED_QUANTITY, variantId: 123 },
13
+ * { operation: 'update', kind: UpdateBasketItemFailureKind.ITEM_ADDED_WITH_REDUCED_QUANTITY, variantId: 456 },
14
+ * ]
15
+ *
16
+ * const allReducedQuantity = wasAddedWithReducedQuantity(errors) // true
17
+ *
18
+ * const noErrors = wasAddedWithReducedQuantity() // false
19
+ *
20
+ * const otherErrors = wasAddedWithReducedQuantity([
21
+ * { operation: 'add', kind: AddToBasketFailureKind.ITEM_NOT_FOUND, variantId: 789 },
22
+ * ]) // false
23
+ * ```
24
+ */
2
25
  export declare const wasAddedWithReducedQuantity: (errors?: AddOrUpdateItemError[]) => boolean;
@@ -0,0 +1,23 @@
1
+ import type { Campaign } from '@scayle/storefront-api';
2
+ /**
3
+ * Determine if a campaign is active for the current date
4
+ *
5
+ * @param campaign the campaign to test
6
+ * @returns True when the current datetime is within the campaign's timeframe, false otherwise
7
+ */
8
+ export declare const isCampaignActive: (campaign?: Campaign) => boolean;
9
+ /**
10
+ * Determine if a campaign has ended based on the current datetime
11
+ *
12
+ * @param campaign
13
+ * @returns True if the campaign's end date is later than the current datetime, false otherwise
14
+ */
15
+ export declare const campaignHasNotEnded: (campaign: Campaign) => boolean;
16
+ /**
17
+ * Comparison function for campaigns based on their start date. To be used with Array.prototype.sort()
18
+ *
19
+ * @param a The first campaign for comparison
20
+ * @param b The second campaign for comparison
21
+ * @returns A positive value when a should come before b, negative value when b should come before a, or 0 when they are considered equal
22
+ */
23
+ 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
+ };
@@ -1,6 +1,25 @@
1
1
  /**
2
- * A custom Error type for errors that occur in a fetch() request
3
- * Contains the Response object from the request, and optionally the response data
2
+ * A custom Error type for errors that occur during a `fetch()` request.
3
+ * Includes the `Response` object and optional response data for more detailed error analysis.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * try {
8
+ * const response = await fetch('/some/url')
9
+ *
10
+ * if (!response.ok) {
11
+ * const data = await response.json(); // Attempt to parse JSON error data
12
+ * throw new FetchError(response, data)
13
+ * }
14
+ * // ... process successful response
15
+ * } catch (error) {
16
+ * if (error instanceof FetchError) {
17
+ * console.error("Fetch error:", error.message, error.response.status, error.data)
18
+ * } else {
19
+ * console.error("Other error:", error)
20
+ * }
21
+ * }
22
+ * ```
4
23
  */
5
24
  export declare class FetchError extends Error {
6
25
  response: Response;
@@ -1,8 +1,75 @@
1
+ /**
2
+ * Calculates the MD5 hash of a string.
3
+ * Uses `CryptoJS` for MD5 calculation. Throws an error if MD5 support is omitted in the build.
4
+ *
5
+ * @see https://cryptojs.gitbook.io/docs/
6
+ *
7
+ * @param value The string to hash.
8
+ *
9
+ * @returns A Promise that resolves with the MD5 hash as a hex string.
10
+ *
11
+ * @throws {Error} If SFC is built without MD5 support (SFC_OMIT_MD5 environment variable is set).
12
+ */
1
13
  export declare const md5: (value: string) => Promise<any>;
14
+ /**
15
+ * Calculates the SHA256 hash of a string.
16
+ *
17
+ * @param value The input string.
18
+ *
19
+ * @returns A Promise that resolves with the SHA256 hash as a hexadecimal string.
20
+ */
2
21
  export declare const sha256: (value: string) => Promise<string>;
22
+ /**
23
+ * Calculates the HMAC (Hash-based Message Authentication Code) of a string.
24
+ *
25
+ * @param value The input string (message).
26
+ * @param secret The secret key.
27
+ * @param algorithm The hashing algorithm to use (e.g., 'SHA-256'). Defaults to 'SHA-256'.
28
+ *
29
+ * @returns A Promise that resolves with the HMAC as a hexadecimal string.
30
+ */
3
31
  export declare const hmac: (value: string, secret: string, algorithm?: string) => Promise<string>;
32
+ /**
33
+ * Encodes a string to Base64. Works in both Node.js and browser environments.
34
+ *
35
+ * @param string The string to encode.
36
+ *
37
+ * @returns The Base64 encoded string.
38
+ */
4
39
  export declare const encodeBase64: (string: string) => string;
40
+ /**
41
+ * Decodes a Base64 encoded string. Works in both Node.js and browser environments.
42
+ *
43
+ * @param string The Base64 encoded string.
44
+ *
45
+ * @returns The decoded string.
46
+ */
5
47
  export declare const decodeBase64: (string: string) => string;
48
+ /**
49
+ * Verifies an order success token by comparing its signature with an expected signature.
50
+ *
51
+ * @template T The type of the parsed payload.
52
+ *
53
+ * @param token The token to verify.
54
+ * @param secret The secret key used to generate the signature.
55
+ *
56
+ * @returns A Promise resolving to the parsed token payload if the signature is valid, or undefined otherwise.
57
+ */
6
58
  export declare const verifyOrderSuccessToken: <T = unknown>(token: string, secret: string) => Promise<T | undefined>;
59
+ /**
60
+ * Builds a hash payload by stringifying and Base64 encoding a given payload.
61
+ *
62
+ * @param payload The payload to hash.
63
+ *
64
+ * @returns The Base64 encoded hash payload.
65
+ */
7
66
  export declare const buildHashPayload: (payload: unknown) => string;
67
+ /**
68
+ * Builds a signature for a given payload hash and secret.
69
+ *
70
+ * @param payloadHash The hash of the payload.
71
+ * @param secret The secret key.
72
+ *
73
+ * @returns A Promise resolving to the Base64 encoded signature.
74
+ */
8
75
  export declare const buildSignature: (payloadHash: string, secret: string) => Promise<string>;
@@ -56,10 +56,10 @@ export const decodeBase64 = (string) => {
56
56
  export const verifyOrderSuccessToken = async (token, secret) => {
57
57
  const [encodedPayload, signature] = token.split(".", 2);
58
58
  const expectedSignature = encodeBase64(await hmac(encodedPayload, secret));
59
- if (expectedSignature === signature) {
60
- return JSON.parse(decodeBase64(encodedPayload));
59
+ if (expectedSignature !== signature) {
60
+ return;
61
61
  }
62
- return void 0;
62
+ return JSON.parse(decodeBase64(encodedPayload));
63
63
  };
64
64
  export const buildHashPayload = (payload) => encodeBase64(JSON.stringify(payload));
65
65
  export const buildSignature = async (payloadHash, secret) => encodeBase64(await hmac(payloadHash, secret));
@@ -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,20 @@
1
1
  import type { Log } from '..';
2
2
  import { HashAlgorithm } from '../constants/hash';
3
+ /**
4
+ * Generates a key based on a template, shop ID, user ID, and optional hash algorithm.
5
+ *
6
+ * @param params The required asynchronous function parameters.
7
+ * @param params.keyName Deprecated and unused value to specify the name of a key.
8
+ * @param params.keyTemplate The template string for the key, which can include placeholders for `shopId` and `userId`.
9
+ * @param params.hashAlgorithm The hash algorithm to use (MD5, SHA256, or none). Defaults to SHA256.
10
+ * @param params.shopId The shop ID.
11
+ * @param params.userId The user ID.
12
+ * @param params.log Optional logger instance.
13
+ *
14
+ * @returns A Promise resolving to the generated key.
15
+ */
3
16
  export declare const generateKey: ({ keyTemplate, hashAlgorithm, shopId, userId, log, }: {
17
+ /** @deprecated */
4
18
  keyName?: string;
5
19
  keyTemplate: string;
6
20
  hashAlgorithm: HashAlgorithm;
@@ -8,6 +22,18 @@ export declare const generateKey: ({ keyTemplate, hashAlgorithm, shopId, userId,
8
22
  userId: string;
9
23
  log?: Log;
10
24
  }) => Promise<string>;
25
+ /**
26
+ * Generates a wishlist key.
27
+ *
28
+ * @param params The required asynchronous function parameters.
29
+ * @param params.keyTemplate The template string for the key.
30
+ * @param params.hashAlgorithm The hash algorithm to use. Defaults to SHA256.
31
+ * @param params.shopId The shop ID.
32
+ * @param params.userId The user ID.
33
+ * @param params.log Optional logger instance.
34
+ *
35
+ * @returns A Promise resolving to the generated wishlist key.
36
+ */
11
37
  export declare const generateWishlistKey: ({ keyTemplate, hashAlgorithm, shopId, userId, log, }: {
12
38
  keyTemplate: string;
13
39
  hashAlgorithm: HashAlgorithm;
@@ -15,6 +41,18 @@ export declare const generateWishlistKey: ({ keyTemplate, hashAlgorithm, shopId,
15
41
  userId: string;
16
42
  log?: Log;
17
43
  }) => Promise<string>;
44
+ /**
45
+ * Generates a basket key.
46
+ *
47
+ * @param params The required asynchronous function parameters.
48
+ * @param params.keyTemplate The template string for the key.
49
+ * @param params.hashAlgorithm The hash algorithm to use. Defaults to SHA256.
50
+ * @param params.shopId The shop ID.
51
+ * @param params.userId The user ID.
52
+ * @param params.log Optional logger instance.
53
+ *
54
+ * @returns A Promise resolving to the generated basket key.
55
+ */
18
56
  export declare const generateBasketKey: ({ keyTemplate, hashAlgorithm, shopId, userId, log, }: {
19
57
  keyTemplate: string;
20
58
  hashAlgorithm: HashAlgorithm;