@scayle/storefront-core 8.61.2 → 8.62.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 +37 -0
- package/dist/api/customer.mjs +1 -1
- package/dist/rpc/methods/checkout/checkout.mjs +1 -1
- package/dist/rpc/methods/promotion.d.ts +29 -7
- package/dist/rpc/methods/promotion.mjs +36 -8
- package/dist/rpc/methods/session.mjs +4 -4
- package/dist/rpc/methods/user.mjs +1 -1
- package/dist/types/api/context.d.ts +2 -2
- package/dist/utils/user.mjs +4 -2
- package/package.json +7 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @scayle/storefront-core
|
|
2
2
|
|
|
3
|
+
## 8.62.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **\[Promotions\]** Added customer-segmented promotion fetches when a valid session token is present.
|
|
8
|
+
|
|
9
|
+
`getPromotions`, `getCurrentPromotions`, and `getPromotionsByIds` accept a `segmentation` flag. When it is `true` and the request has a non-expired access token, the handlers call the Storefront API with `customerToken` and skip the shared cache so results match the logged-in customer. When it is `false`, handlers use the cached anonymous promotion list so customer-specific promotions are not stored in shared cache responses.
|
|
10
|
+
|
|
11
|
+
**\[BREAKING\]** `getPromotionsByIds` now expects `{ ids: string[], segmentation: boolean }` instead of a bare `string[]` array.
|
|
12
|
+
- Before (custom RPC handler via `context.callRpc`):
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
const promotions = await context.callRpc?.('getPromotionsByIds', [
|
|
16
|
+
'promo-1',
|
|
17
|
+
'promo-2',
|
|
18
|
+
])
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- After:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
const promotions = await context.callRpc?.('getPromotionsByIds', {
|
|
25
|
+
ids: ['promo-1', 'promo-2'],
|
|
26
|
+
segmentation: true,
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Promotion list handlers use `POST` instead of `GET` so `segmentation` and `customerToken` are not sent in the URL.
|
|
31
|
+
|
|
32
|
+
**\[User\]** Exported `getValidatedAccessToken` to return a JWT only when it is present and not expired. Basket merge and promotion handlers use it when passing `customerToken` to the Storefront API.
|
|
33
|
+
|
|
34
|
+
## 8.61.3
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- Fix `updateTokens` and `updateUser` return type from `void` to `Promise<void>` in `ContextWithSession`. The underlying implementations were already async (`await session.save()`), but the incorrect `void` type prevented call sites from knowing they needed to await the result. All call sites in the RPC methods and customer API now correctly await these calls, ensuring session state is persisted before execution continues.
|
|
39
|
+
|
|
3
40
|
## 8.61.2
|
|
4
41
|
|
|
5
42
|
No changes in this release.
|
package/dist/api/customer.mjs
CHANGED
|
@@ -58,7 +58,7 @@ export class CustomerAPIClient {
|
|
|
58
58
|
grant_type: "refresh_token",
|
|
59
59
|
refresh_token: this.context.refreshToken
|
|
60
60
|
});
|
|
61
|
-
this.context.updateTokens({
|
|
61
|
+
await this.context.updateTokens({
|
|
62
62
|
accessToken: tokens?.access_token,
|
|
63
63
|
refreshToken: tokens?.refresh_token
|
|
64
64
|
});
|
|
@@ -41,7 +41,7 @@ export const getCheckoutToken = defineRpcHandler(
|
|
|
41
41
|
...customData,
|
|
42
42
|
...orderCustomData
|
|
43
43
|
}
|
|
44
|
-
}).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"8.
|
|
44
|
+
}).setIssuedAt(now).setNotBefore(now).setExpirationTime("1h").setIssuer(`${"@scayle/storefront-core"}@${"8.62.0"}`).setProtectedHeader({ alg: "HS256", typ: "JWT" }).sign(secret);
|
|
45
45
|
return {
|
|
46
46
|
accessToken: refreshedAccessToken,
|
|
47
47
|
checkoutJwt
|
|
@@ -2,45 +2,67 @@ import type { PromotionsParams, PromotionsResponseData, RpcHandler } from '../..
|
|
|
2
2
|
/**
|
|
3
3
|
* Retrieves promotions based on provided parameters.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Uses the Storefront cache (`cached()`) with a `getPromotions` key prefix to improve performance.
|
|
6
6
|
* Cached entries are returned if found; otherwise, data is fetched and cached.
|
|
7
7
|
*
|
|
8
|
+
* When `segmentation` is `true` and the request includes a valid customer access token, promotions are
|
|
9
|
+
* fetched directly from the Storefront API and are not cached.
|
|
10
|
+
*
|
|
8
11
|
* @param params The parameters for retrieving promotions.
|
|
9
12
|
* @param params.pagination Pagination settings.
|
|
10
13
|
* @param params.pagination.page Page number.
|
|
11
14
|
* @param params.pagination.perPage Number of items per page.
|
|
15
|
+
* @param params.segmentation When `true`, fetches customer-segmented promotions without caching.
|
|
16
|
+
* Defaults to `false`.
|
|
12
17
|
* @param context The RPC context.
|
|
13
18
|
*
|
|
14
19
|
* @returns A Promise that resolves with the promotion data.
|
|
15
20
|
* It will return an `ErrorResponse` if the The Storefront API request fails.
|
|
16
21
|
*/
|
|
17
|
-
export declare const getPromotions: RpcHandler<Omit<PromotionsParams, 'ids'
|
|
22
|
+
export declare const getPromotions: RpcHandler<Omit<PromotionsParams, 'ids' | 'customerToken'> & {
|
|
23
|
+
segmentation: boolean;
|
|
24
|
+
}, PromotionsResponseData>;
|
|
18
25
|
/**
|
|
19
26
|
* Retrieves currently active promotions.
|
|
20
27
|
*
|
|
21
|
-
*
|
|
28
|
+
* Uses the Storefront cache (`cached()`) with a `getPromotions` key prefix to improve performance.
|
|
22
29
|
* Cached entries are returned if found; otherwise, data is fetched and cached.
|
|
23
30
|
*
|
|
31
|
+
* When `segmentation` is `true` and the request includes a valid customer access token, promotions are
|
|
32
|
+
* fetched directly from the Storefront API and are not cached.
|
|
33
|
+
*
|
|
24
34
|
* @param params The parameters for retrieving promotions.
|
|
25
35
|
* @param params.pagination Pagination settings.
|
|
26
36
|
* @param params.pagination.page Page number.
|
|
27
37
|
* @param params.pagination.perPage Number of items per page.
|
|
38
|
+
* @param params.segmentation When `true`, fetches customer-segmented promotions without caching.
|
|
39
|
+
* Defaults to `false`.
|
|
28
40
|
* @param context The RPC context.
|
|
29
41
|
*
|
|
30
42
|
* @returns A Promise that resolves with the current promotion data.
|
|
31
43
|
* It will return an `ErrorResponse` if the The Storefront API request fails.
|
|
32
44
|
*/
|
|
33
|
-
export declare const getCurrentPromotions: RpcHandler<Omit<PromotionsParams, 'ids' | 'activeAt'
|
|
45
|
+
export declare const getCurrentPromotions: RpcHandler<Omit<PromotionsParams, 'ids' | 'activeAt' | 'customerToken'> & {
|
|
46
|
+
segmentation: boolean;
|
|
47
|
+
}, PromotionsResponseData>;
|
|
34
48
|
/**
|
|
35
49
|
* Retrieves promotions by their IDs.
|
|
36
50
|
*
|
|
37
|
-
*
|
|
51
|
+
* Uses the Storefront cache (`cached()`) with a `getByIds-promotions` key prefix to improve performance.
|
|
38
52
|
* Cached entries are returned if found; otherwise, data is fetched and cached.
|
|
39
53
|
*
|
|
40
|
-
*
|
|
54
|
+
* When `segmentation` is `true` and the request includes a valid customer access token, promotions are
|
|
55
|
+
* fetched directly from the Storefront API and are not cached.
|
|
56
|
+
*
|
|
57
|
+
* @param params Request parameters.
|
|
58
|
+
* @param params.ids Promotion IDs to retrieve.
|
|
59
|
+
* @param params.segmentation When `true`, fetches customer-segmented promotions without caching.
|
|
41
60
|
* @param context The RPC context.
|
|
42
61
|
*
|
|
43
62
|
* @returns A Promise that resolves with the promotion data.
|
|
44
63
|
* It will return an `ErrorResponse` if the The Storefront API request fails.
|
|
45
64
|
*/
|
|
46
|
-
export declare const getPromotionsByIds: RpcHandler<
|
|
65
|
+
export declare const getPromotionsByIds: RpcHandler<{
|
|
66
|
+
ids: string[];
|
|
67
|
+
segmentation: boolean;
|
|
68
|
+
}, PromotionsResponseData>;
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
} from "../../constants/index.mjs";
|
|
5
5
|
import { mapSAPIFetchErrorToResponse } from "../../utils/sapi.mjs";
|
|
6
6
|
import { defineRpcHandler } from "../../utils/index.mjs";
|
|
7
|
+
import { getValidatedAccessToken } from "../../utils/user.mjs";
|
|
7
8
|
const setPaginationDefault = (pagination) => {
|
|
8
9
|
return {
|
|
9
10
|
page: pagination?.page || PROMOTION_PAGE_DEFAULT,
|
|
@@ -11,42 +12,69 @@ const setPaginationDefault = (pagination) => {
|
|
|
11
12
|
};
|
|
12
13
|
};
|
|
13
14
|
export const getPromotions = defineRpcHandler(
|
|
14
|
-
async (params = {}, context) => {
|
|
15
|
+
async (params = { segmentation: false }, context) => {
|
|
15
16
|
const { sapiClient, cached } = context;
|
|
17
|
+
const { segmentation, ...promotionsParams } = params;
|
|
18
|
+
const customerToken = getValidatedAccessToken(context.accessToken);
|
|
19
|
+
if (segmentation && customerToken) {
|
|
20
|
+
return mapSAPIFetchErrorToResponse(sapiClient.promotions.get)({
|
|
21
|
+
...promotionsParams,
|
|
22
|
+
pagination: setPaginationDefault(params.pagination),
|
|
23
|
+
customerToken
|
|
24
|
+
});
|
|
25
|
+
}
|
|
16
26
|
return await cached(
|
|
17
27
|
mapSAPIFetchErrorToResponse(sapiClient.promotions.get),
|
|
18
28
|
{
|
|
19
29
|
cacheKeyPrefix: "getPromotions"
|
|
20
30
|
}
|
|
21
31
|
)({
|
|
22
|
-
...
|
|
32
|
+
...promotionsParams,
|
|
23
33
|
pagination: setPaginationDefault(params.pagination)
|
|
24
34
|
});
|
|
25
35
|
},
|
|
26
|
-
{ method: "
|
|
36
|
+
{ method: "POST" }
|
|
27
37
|
);
|
|
28
38
|
export const getCurrentPromotions = defineRpcHandler(
|
|
29
|
-
async (params = {}, context) => {
|
|
39
|
+
async (params = { segmentation: false }, context) => {
|
|
30
40
|
const { sapiClient, cached } = context;
|
|
31
41
|
const now = /* @__PURE__ */ new Date();
|
|
32
42
|
now.setSeconds(0, 0);
|
|
33
43
|
const activeAt = now.toISOString();
|
|
44
|
+
const { segmentation, ...promotionsParams } = params;
|
|
45
|
+
const customerToken = getValidatedAccessToken(context.accessToken);
|
|
46
|
+
if (segmentation && customerToken) {
|
|
47
|
+
return mapSAPIFetchErrorToResponse(sapiClient.promotions.get)({
|
|
48
|
+
...promotionsParams,
|
|
49
|
+
activeAt,
|
|
50
|
+
pagination: setPaginationDefault(params.pagination),
|
|
51
|
+
customerToken
|
|
52
|
+
});
|
|
53
|
+
}
|
|
34
54
|
return await cached(
|
|
35
55
|
mapSAPIFetchErrorToResponse(sapiClient.promotions.get),
|
|
36
56
|
{
|
|
37
57
|
cacheKeyPrefix: "getPromotions"
|
|
38
58
|
}
|
|
39
59
|
)({
|
|
40
|
-
...
|
|
60
|
+
...promotionsParams,
|
|
41
61
|
activeAt,
|
|
42
62
|
pagination: setPaginationDefault(params.pagination)
|
|
43
63
|
});
|
|
44
64
|
},
|
|
45
|
-
{ method: "
|
|
65
|
+
{ method: "POST" }
|
|
46
66
|
);
|
|
47
67
|
export const getPromotionsByIds = defineRpcHandler(
|
|
48
|
-
async (
|
|
68
|
+
async (params, context) => {
|
|
49
69
|
const { sapiClient, cached } = context;
|
|
70
|
+
const { ids, segmentation } = params;
|
|
71
|
+
const customerToken = getValidatedAccessToken(context.accessToken);
|
|
72
|
+
if (segmentation && customerToken) {
|
|
73
|
+
return mapSAPIFetchErrorToResponse(sapiClient.promotions.getByIds)(
|
|
74
|
+
ids,
|
|
75
|
+
customerToken
|
|
76
|
+
);
|
|
77
|
+
}
|
|
50
78
|
return await cached(
|
|
51
79
|
mapSAPIFetchErrorToResponse(sapiClient.promotions.getByIds),
|
|
52
80
|
{
|
|
@@ -54,5 +82,5 @@ export const getPromotionsByIds = defineRpcHandler(
|
|
|
54
82
|
}
|
|
55
83
|
)(ids);
|
|
56
84
|
},
|
|
57
|
-
{ method: "
|
|
85
|
+
{ method: "POST" }
|
|
58
86
|
);
|
|
@@ -36,11 +36,11 @@ export async function postLogin(context, tokens) {
|
|
|
36
36
|
return fetchUserResponse;
|
|
37
37
|
}
|
|
38
38
|
const user = await unwrap(fetchUserResponse);
|
|
39
|
-
context.updateTokens({
|
|
39
|
+
await context.updateTokens({
|
|
40
40
|
accessToken: tokens.access_token,
|
|
41
41
|
refreshToken: tokens.refresh_token
|
|
42
42
|
});
|
|
43
|
-
context.updateUser(user);
|
|
43
|
+
await context.updateUser(user);
|
|
44
44
|
const { customerId } = decodeJwt(tokens.access_token);
|
|
45
45
|
await Promise.all([
|
|
46
46
|
mergeBaskets(
|
|
@@ -201,7 +201,7 @@ export const refreshAccessToken = defineRpcHandler(
|
|
|
201
201
|
grant_type: "refresh_token",
|
|
202
202
|
refresh_token: refreshToken
|
|
203
203
|
});
|
|
204
|
-
context.updateTokens({
|
|
204
|
+
await context.updateTokens({
|
|
205
205
|
accessToken: tokens?.access_token,
|
|
206
206
|
refreshToken: tokens?.refresh_token
|
|
207
207
|
});
|
|
@@ -337,7 +337,7 @@ export const updatePasswordByHash = defineRpcHandler(
|
|
|
337
337
|
...passwordHash,
|
|
338
338
|
shop_id: shopId
|
|
339
339
|
});
|
|
340
|
-
context.updateTokens({
|
|
340
|
+
await context.updateTokens({
|
|
341
341
|
accessToken: tokens.access_token,
|
|
342
342
|
refreshToken: tokens.refresh_token
|
|
343
343
|
});
|
|
@@ -98,7 +98,7 @@ const getAccessToken = defineRpcHandler(
|
|
|
98
98
|
grant_type: "refresh_token",
|
|
99
99
|
refresh_token: context.refreshToken
|
|
100
100
|
});
|
|
101
|
-
context.updateTokens({
|
|
101
|
+
await context.updateTokens({
|
|
102
102
|
accessToken: tokens.access_token,
|
|
103
103
|
refreshToken: tokens.refresh_token
|
|
104
104
|
});
|
|
@@ -72,8 +72,8 @@ export interface ContextWithSession {
|
|
|
72
72
|
destroySession: () => Promise<void>;
|
|
73
73
|
createUserBoundSession: () => Promise<void>;
|
|
74
74
|
updateSessionCustomData: (updatedCustomData: SessionCustomData) => Promise<void>;
|
|
75
|
-
updateUser: (user: ShopUser) => void
|
|
76
|
-
updateTokens: (tokens: OAuthTokens) => void
|
|
75
|
+
updateUser: (user: ShopUser) => Promise<void>;
|
|
76
|
+
updateTokens: (tokens: OAuthTokens) => Promise<void>;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
79
|
* Context interface without session information.
|
package/dist/utils/user.mjs
CHANGED
|
@@ -6,7 +6,7 @@ export const getValidatedAccessToken = (accessToken) => {
|
|
|
6
6
|
}
|
|
7
7
|
const payload = decodeJwt(accessToken);
|
|
8
8
|
if (!payload.exp || payload.exp < Date.now() / 1e3) {
|
|
9
|
-
return;
|
|
9
|
+
return void 0;
|
|
10
10
|
}
|
|
11
11
|
return accessToken;
|
|
12
12
|
};
|
|
@@ -51,7 +51,9 @@ export const mergeBaskets = async (fromBasketKey, toBasketKey, withOptions, cont
|
|
|
51
51
|
}
|
|
52
52
|
);
|
|
53
53
|
fromBasket.items.map(async (item) => {
|
|
54
|
-
return await sapiClient.basket.deleteItem(fromBasketKey, item.key
|
|
54
|
+
return await sapiClient.basket.deleteItem(fromBasketKey, item.key, {
|
|
55
|
+
customerToken: getValidatedAccessToken(context.accessToken) ?? void 0
|
|
56
|
+
});
|
|
55
57
|
});
|
|
56
58
|
return mergedBasket;
|
|
57
59
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scayle/storefront-core",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.62.0",
|
|
4
4
|
"description": "Collection of essential utilities to work with the Storefront API",
|
|
5
5
|
"author": "SCAYLE Commerce Engine",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"fishery": "^2.2.3"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@scayle/storefront-api": "^19.
|
|
44
|
+
"@scayle/storefront-api": "^19.1.0",
|
|
45
45
|
"@scayle/unstorage-scayle-kv-driver": "^2.1.0",
|
|
46
46
|
"crypto-js": "^4.2.0",
|
|
47
47
|
"hookable": "^5.5.3",
|
|
@@ -56,20 +56,19 @@
|
|
|
56
56
|
"@types/crypto-js": "4.2.2",
|
|
57
57
|
"@types/node": "24.12.2",
|
|
58
58
|
"@types/webpack-env": "1.18.8",
|
|
59
|
-
"@vitest/coverage-v8": "4.1.
|
|
59
|
+
"@vitest/coverage-v8": "4.1.7",
|
|
60
60
|
"eslint-formatter-gitlab": "7.1.0",
|
|
61
|
-
"eslint": "10.
|
|
61
|
+
"eslint": "10.4.0",
|
|
62
62
|
"fishery": "2.4.0",
|
|
63
|
-
"publint": "0.3.
|
|
64
|
-
"rimraf": "6.1.3",
|
|
63
|
+
"publint": "0.3.21",
|
|
65
64
|
"typescript": "6.0.3",
|
|
66
65
|
"unbuild": "3.6.1",
|
|
67
66
|
"unstorage": "1.17.5",
|
|
68
|
-
"vitest": "4.1.
|
|
67
|
+
"vitest": "4.1.7",
|
|
69
68
|
"@scayle/vitest-config-storefront": "1.0.0"
|
|
70
69
|
},
|
|
71
70
|
"scripts": {
|
|
72
|
-
"clean": "
|
|
71
|
+
"clean": "node -e \"require('fs').rmSync('./dist',{recursive:true,force:true})\"",
|
|
73
72
|
"build": "unbuild",
|
|
74
73
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
75
74
|
"lint": "eslint .",
|