cabloy 5.1.112 → 5.1.114
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/.cabloy-version +1 -1
- package/.claude/skills/cabloy-backend-scaffold/SKILL.md +2 -0
- package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +13 -0
- package/.github/workflows/playwright-e2e.yml +34 -2
- package/.github/workflows/vona-test-mysql.yml +12 -0
- package/.github/workflows/vona-test-pg.yml +12 -0
- package/CHANGELOG.md +27 -0
- package/CLAUDE.md +6 -0
- package/cabloy-docs/backend/internal-aop-guide.md +35 -0
- package/cabloy-docs/backend/migration-and-changes.md +13 -9
- package/cabloy-docs/backend/unit-testing.md +97 -1
- package/e2e/scripts/startE2eVona.ts +51 -13
- package/e2e/specs/a-commerce/commerce.spec.ts +1 -0
- package/e2e/specs/cabloy-basic/basic.spec.ts +12 -7
- package/package.json +1 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.test.ts +6 -9
- package/vona/packages-cli/cli-set-api/toolsIsolate/test.ts +74 -32
- package/vona/packages-utils/compose/package.json +1 -1
- package/vona/packages-utils/compose/src/index.ts +19 -11
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/bean/beanContainer.ts +15 -3
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +82 -67
- package/vona/src/backend/config/config/config.test.ts +1 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +8 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/bean/meta.version.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +3 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuAttribute.tsx +17 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/entity/sku.tsx +10 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/catalog.test.ts +234 -0
- package/vona/src/suite/a-commerce/modules/commerce-member/test/addressOwnership.test.ts +6 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/package.json +2 -1
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +228 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.index.ts +15 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.version.ts +35 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/dto/paymentAttemptView.tsx +26 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/paymentAttempt.tsx +34 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/model/paymentAttempt.ts +10 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/paymentAttempt.ts +41 -0
- package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentAttempt.test.ts +33 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/package.json +4 -2
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +533 -2
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/locales.ts +18 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/bean/meta.index.ts +24 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/bean/meta.version.ts +137 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/bean/ssrMenu.couponTemplate.ts +30 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/config/locale/en-us.ts +37 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/config/locale/zh-cn.ts +37 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/controller/coupon.ts +37 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/controller/couponTemplate.ts +65 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponIssue.tsx +22 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponMineItem.tsx +32 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateCreate.tsx +43 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateSelectReq.tsx +28 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateSelectRes.tsx +11 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateSelectResItem.tsx +39 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateUpdate.tsx +19 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponTemplateView.tsx +25 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/entity/couponAudit.tsx +73 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/entity/couponGrant.tsx +83 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/entity/couponTemplate.tsx +82 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/index.ts +1 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/model/couponAudit.ts +10 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/model/couponGrant.ts +10 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/model/couponTemplate.ts +10 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/service/coupon.ts +397 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/src/service/couponTemplate.ts +52 -0
- package/vona/src/suite/a-commerce/modules/commerce-promotion/test/couponReservation.test.ts +162 -0
- package/vona/src/suite/a-commerce/modules/commerce-seed/src/bean/meta.version.ts +11 -13
- package/vona/src/suite/a-commerce/modules/commerce-trade/package.json +4 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +277 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/bean.scheduleOrderExpiry.ts +26 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/meta.index.ts +9 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/meta.version.ts +79 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +14 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +14 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/checkout.ts +19 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutCreate.tsx +19 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutResult.tsx +32 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderAddressSnapshot.tsx +33 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderCouponSnapshot.tsx +35 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderLineSkuAttributeSnapshot.tsx +15 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/order.tsx +90 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/orderAudit.tsx +36 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/orderLine.tsx +52 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/stockAudit.tsx +39 -7
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/stockReservation.tsx +4 -1
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +15 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/orderAudit.ts +10 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/orderLine.ts +10 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +556 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/stockBalance.ts +32 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/cartOwnership.test.ts +8 -3
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutReservation.test.ts +158 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/orderSnapshot.test.ts +203 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/reservationExpiry.test.ts +163 -0
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockBalance.test.ts +274 -66
- package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockReservation.test.ts +282 -82
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +1 -1
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +10 -7
- package/vona/src/suite-vendor/a-image/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-aspect/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-aspect/src/types/aopMethod.ts +16 -3
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +4 -2
- package/vona/src/suite-vendor/a-vona/modules/a-core/src/bean/bean.core.ts +6 -0
- package/vona/src/suite-vendor/a-vona/modules/a-core/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-core/src/lib/core.ts +6 -0
- package/vona/src/suite-vendor/a-vona/modules/a-core/src/types/fetch.ts +7 -0
- package/vona/src/suite-vendor/a-vona/modules/a-core/src/types/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/lib/utils.ts +5 -3
- package/vona/src/suite-vendor/a-vona/modules/a-retryable/package.json +50 -0
- package/vona/src/suite-vendor/a-vona/modules/a-retryable/src/.metadata/index.ts +55 -0
- package/vona/src/suite-vendor/a-vona/modules/a-retryable/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-vona/modules/a-retryable/src/bean/aopMethod.retryable.ts +45 -0
- package/vona/src/suite-vendor/a-vona/modules/a-retryable/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-retryable/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-vona/modules/a-retryable/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/version.ts +12 -12
- package/vona/src/suite-vendor/a-vona/modules/a-version/src/types/version.ts +4 -4
- package/vona/src/suite-vendor/a-vona/modules/a-vona/package.json +2 -1
- package/vona/src/suite-vendor/a-vona/package.json +2 -1
- package/vona/src/suite-vendor/a-vona/tsconfig.json +3 -0
- package/zova/packages-utils/zova-jsx/package.json +3 -3
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +2 -2
- package/zova/pnpm-lock.yaml +14 -14
- package/zova/src/suite/a-commerce/modules/commerce-catalog/src/page/product/controller.tsx +0 -1
- package/zova/src/suite/a-commerce/modules/commerce-promotion/cli/openapi.config.ts +9 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/package.json +2 -1
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/.metadata/index.ts +115 -1
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/commercePromotionCoupon.ts +24 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/baseURL.ts +5 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/index.ts +3 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/schemas.ts +478 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/types.ts +8187 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/apiSchema/commercePromotionCoupon.ts +13 -0
- package/zova/src/suite/a-commerce/modules/commerce-promotion/src/model/coupon.ts +19 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +30 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeCheckout.ts +27 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +92 -52
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +1389 -848
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeCheckout.ts +13 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/cart.ts +14 -0
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx +17 -7
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.header.tsx +1 -1
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx +17 -7
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +3 -3
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
|
@@ -2,35 +2,78 @@ import type { EntityStockReservation } from 'vona-module-commerce-trade';
|
|
|
2
2
|
|
|
3
3
|
import { catchError } from '@cabloy/utils';
|
|
4
4
|
import assert from 'node:assert';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
5
6
|
import { describe, it } from 'node:test';
|
|
6
7
|
import { app } from 'vona-mock';
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
interface IStockFixture {
|
|
10
|
+
categoryId: number;
|
|
11
|
+
productId: number;
|
|
12
|
+
skuId: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type IStockFixturePartial = Partial<IStockFixture>;
|
|
16
|
+
|
|
17
|
+
function createTestId() {
|
|
18
|
+
return randomUUID().slice(0, 12);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function createSku(suffix: string, fixtures: IStockFixturePartial[]): Promise<IStockFixture> {
|
|
22
|
+
const fixture: IStockFixturePartial = {};
|
|
23
|
+
fixtures.push(fixture);
|
|
24
|
+
fixture.categoryId = await app.bean.executor.performAction('post', '/commerce/catalog/category', {
|
|
10
25
|
body: { name: `reservation-category-${suffix}`, published: true },
|
|
11
26
|
});
|
|
12
|
-
|
|
13
|
-
body: {
|
|
27
|
+
fixture.productId = await app.bean.executor.performAction('post', '/commerce/catalog/product', {
|
|
28
|
+
body: {
|
|
29
|
+
categoryId: fixture.categoryId,
|
|
30
|
+
title: `reservation-product-${suffix}`,
|
|
31
|
+
published: true,
|
|
32
|
+
},
|
|
14
33
|
});
|
|
15
|
-
|
|
34
|
+
fixture.skuId = await app.bean.executor.performAction('post', '/commerce/catalog/sku', {
|
|
16
35
|
body: {
|
|
17
|
-
productId,
|
|
36
|
+
productId: fixture.productId,
|
|
18
37
|
code: `reservation-sku-${suffix}`,
|
|
19
38
|
priceCents: 100,
|
|
20
39
|
lifecycle: 'active',
|
|
21
40
|
},
|
|
22
41
|
});
|
|
42
|
+
return fixture as IStockFixture;
|
|
23
43
|
}
|
|
24
44
|
|
|
25
|
-
async function prepareStock(
|
|
26
|
-
|
|
45
|
+
async function prepareStock(
|
|
46
|
+
suffix: string,
|
|
47
|
+
fixtures: IStockFixturePartial[],
|
|
48
|
+
quantity = 3,
|
|
49
|
+
): Promise<IStockFixture> {
|
|
50
|
+
const fixture = await createSku(suffix, fixtures);
|
|
27
51
|
await app.scope('commerce-trade').service.stockBalance.adjustStock({
|
|
28
|
-
skuId,
|
|
52
|
+
skuId: fixture.skuId,
|
|
29
53
|
delta: quantity,
|
|
30
54
|
reason: 'reservation test setup',
|
|
31
55
|
correlationId: `reservation-setup-${suffix}`,
|
|
32
56
|
});
|
|
33
|
-
return
|
|
57
|
+
return fixture;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function dropStockFixtures(fixtures: IStockFixturePartial[]) {
|
|
61
|
+
const scopeTrade = app.scope('commerce-trade');
|
|
62
|
+
const scopeCatalog = app.scope('commerce-catalog');
|
|
63
|
+
for (const fixture of fixtures.toReversed()) {
|
|
64
|
+
if (fixture.skuId !== undefined) {
|
|
65
|
+
await scopeTrade.model.stockAudit.delete({ skuId: fixture.skuId });
|
|
66
|
+
await scopeTrade.model.stockReservation.delete({ skuId: fixture.skuId });
|
|
67
|
+
await scopeTrade.model.stockBalance.delete({ skuId: fixture.skuId });
|
|
68
|
+
await scopeCatalog.model.sku.delete({ id: fixture.skuId });
|
|
69
|
+
}
|
|
70
|
+
if (fixture.productId !== undefined) {
|
|
71
|
+
await scopeCatalog.model.product.delete({ id: fixture.productId });
|
|
72
|
+
}
|
|
73
|
+
if (fixture.categoryId !== undefined) {
|
|
74
|
+
await scopeCatalog.model.category.delete({ id: fixture.categoryId });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
34
77
|
}
|
|
35
78
|
|
|
36
79
|
async function reserve(
|
|
@@ -59,17 +102,18 @@ async function assertReservationRejected(skuId: number, suffix: string, expected
|
|
|
59
102
|
);
|
|
60
103
|
}
|
|
61
104
|
|
|
62
|
-
describe('stockReservation.test.ts', () => {
|
|
105
|
+
describe('stockReservation.test.ts', { concurrency: false }, () => {
|
|
63
106
|
it('reserves and consumes stock exactly once with traceable audits', async () => {
|
|
64
107
|
await app.bean.executor.mockCtx(async () => {
|
|
65
|
-
const
|
|
108
|
+
const fixtures: IStockFixturePartial[] = [];
|
|
66
109
|
await app.bean.passport.signinMock();
|
|
67
110
|
try {
|
|
68
|
-
const
|
|
69
|
-
const
|
|
111
|
+
const suffix = createTestId();
|
|
112
|
+
const fixture = await prepareStock(suffix, fixtures);
|
|
113
|
+
const reservation = await reserve(fixture.skuId, suffix);
|
|
70
114
|
const balanceAfterReserve = await app
|
|
71
115
|
.scope('commerce-trade')
|
|
72
|
-
.model.stockBalance.get({ skuId });
|
|
116
|
+
.model.stockBalance.get({ skuId: fixture.skuId });
|
|
73
117
|
assert.deepEqual(
|
|
74
118
|
[
|
|
75
119
|
balanceAfterReserve?.onHand,
|
|
@@ -79,7 +123,7 @@ describe('stockReservation.test.ts', () => {
|
|
|
79
123
|
[3, 2, 1],
|
|
80
124
|
);
|
|
81
125
|
|
|
82
|
-
const duplicate = await reserve(skuId, suffix);
|
|
126
|
+
const duplicate = await reserve(fixture.skuId, suffix);
|
|
83
127
|
assert.equal(duplicate.id, reservation.id);
|
|
84
128
|
const consumed = await app.scope('commerce-trade').service.stockBalance.consume({
|
|
85
129
|
reservationId: reservation.id,
|
|
@@ -94,7 +138,7 @@ describe('stockReservation.test.ts', () => {
|
|
|
94
138
|
|
|
95
139
|
const balanceAfterConsume = await app
|
|
96
140
|
.scope('commerce-trade')
|
|
97
|
-
.model.stockBalance.get({ skuId });
|
|
141
|
+
.model.stockBalance.get({ skuId: fixture.skuId });
|
|
98
142
|
assert.deepEqual(
|
|
99
143
|
[
|
|
100
144
|
balanceAfterConsume?.onHand,
|
|
@@ -103,15 +147,16 @@ describe('stockReservation.test.ts', () => {
|
|
|
103
147
|
],
|
|
104
148
|
[1, 0, 1],
|
|
105
149
|
);
|
|
106
|
-
const audits = await app
|
|
107
|
-
.
|
|
108
|
-
|
|
150
|
+
const audits = await app.scope('commerce-trade').model.stockAudit.select({
|
|
151
|
+
where: { skuId: fixture.skuId },
|
|
152
|
+
});
|
|
109
153
|
assert.deepEqual(
|
|
110
154
|
audits.map(audit => audit.operation),
|
|
111
155
|
['adjust', 'reserve', 'consume'],
|
|
112
156
|
);
|
|
113
157
|
assert.equal(audits[1].stockReservationId, reservation.id);
|
|
114
158
|
} finally {
|
|
159
|
+
await dropStockFixtures(fixtures);
|
|
115
160
|
await app.bean.passport.signout();
|
|
116
161
|
}
|
|
117
162
|
});
|
|
@@ -119,36 +164,29 @@ describe('stockReservation.test.ts', () => {
|
|
|
119
164
|
|
|
120
165
|
it('rejects reservations for inactive or unpublished catalog data without writes', async () => {
|
|
121
166
|
await app.bean.executor.mockCtx(async () => {
|
|
122
|
-
const
|
|
167
|
+
const fixtures: IStockFixturePartial[] = [];
|
|
123
168
|
await app.bean.passport.signinMock();
|
|
124
169
|
try {
|
|
125
|
-
const
|
|
126
|
-
await
|
|
170
|
+
const suffix = createTestId();
|
|
171
|
+
const inactiveSku = await prepareStock(`${suffix}-inactive`, fixtures);
|
|
172
|
+
await app.scope('commerce-catalog').model.sku.updateById(inactiveSku.skuId, {
|
|
127
173
|
lifecycle: 'inactive',
|
|
128
174
|
});
|
|
129
|
-
await assertReservationRejected(
|
|
175
|
+
await assertReservationRejected(inactiveSku.skuId, `${suffix}-inactive`, 404);
|
|
130
176
|
|
|
131
|
-
const
|
|
132
|
-
const unpublishedProductSku = await app
|
|
133
|
-
.scope('commerce-catalog')
|
|
134
|
-
.model.sku.getById(unpublishedProductSkuId);
|
|
177
|
+
const unpublishedProductSku = await prepareStock(`${suffix}-product`, fixtures);
|
|
135
178
|
await app
|
|
136
179
|
.scope('commerce-catalog')
|
|
137
|
-
.model.product.updateById(unpublishedProductSku
|
|
138
|
-
await assertReservationRejected(
|
|
180
|
+
.model.product.updateById(unpublishedProductSku.productId, { published: false });
|
|
181
|
+
await assertReservationRejected(unpublishedProductSku.skuId, `${suffix}-product`, 409);
|
|
139
182
|
|
|
140
|
-
const
|
|
141
|
-
const unpublishedCategorySku = await app
|
|
142
|
-
.scope('commerce-catalog')
|
|
143
|
-
.model.sku.getById(unpublishedCategorySkuId);
|
|
144
|
-
const unpublishedCategoryProduct = await app
|
|
145
|
-
.scope('commerce-catalog')
|
|
146
|
-
.model.product.getById(unpublishedCategorySku!.productId);
|
|
183
|
+
const unpublishedCategorySku = await prepareStock(`${suffix}-category`, fixtures);
|
|
147
184
|
await app
|
|
148
185
|
.scope('commerce-catalog')
|
|
149
|
-
.model.category.updateById(
|
|
150
|
-
await assertReservationRejected(
|
|
186
|
+
.model.category.updateById(unpublishedCategorySku.categoryId, { published: false });
|
|
187
|
+
await assertReservationRejected(unpublishedCategorySku.skuId, `${suffix}-category`, 409);
|
|
151
188
|
} finally {
|
|
189
|
+
await dropStockFixtures(fixtures);
|
|
152
190
|
await app.bean.passport.signout();
|
|
153
191
|
}
|
|
154
192
|
});
|
|
@@ -156,11 +194,12 @@ describe('stockReservation.test.ts', () => {
|
|
|
156
194
|
|
|
157
195
|
it('releases and restores stock only from legal reservation states', async () => {
|
|
158
196
|
await app.bean.executor.mockCtx(async () => {
|
|
159
|
-
const
|
|
197
|
+
const fixtures: IStockFixturePartial[] = [];
|
|
160
198
|
await app.bean.passport.signinMock();
|
|
161
199
|
try {
|
|
162
|
-
const
|
|
163
|
-
const
|
|
200
|
+
const suffix = createTestId();
|
|
201
|
+
const fixture = await prepareStock(suffix, fixtures);
|
|
202
|
+
const releasedReservation = await reserve(fixture.skuId, `${suffix}-release`);
|
|
164
203
|
const released = await app.scope('commerce-trade').service.stockBalance.release({
|
|
165
204
|
reservationId: releasedReservation.id,
|
|
166
205
|
reason: 'payment failed',
|
|
@@ -172,7 +211,7 @@ describe('stockReservation.test.ts', () => {
|
|
|
172
211
|
});
|
|
173
212
|
assert.equal(duplicateRelease.state, 'released');
|
|
174
213
|
|
|
175
|
-
const consumedReservation = await reserve(skuId, `${suffix}-restore`, 1);
|
|
214
|
+
const consumedReservation = await reserve(fixture.skuId, `${suffix}-restore`, 1);
|
|
176
215
|
await app.scope('commerce-trade').service.stockBalance.consume({
|
|
177
216
|
reservationId: consumedReservation.id,
|
|
178
217
|
reason: 'payment success',
|
|
@@ -182,6 +221,11 @@ describe('stockReservation.test.ts', () => {
|
|
|
182
221
|
reason: 'refund success',
|
|
183
222
|
});
|
|
184
223
|
assert.equal(restored.state, 'restored');
|
|
224
|
+
const duplicateRestore = await app.scope('commerce-trade').service.stockBalance.restore({
|
|
225
|
+
reservationId: consumedReservation.id,
|
|
226
|
+
reason: 'refund success retry',
|
|
227
|
+
});
|
|
228
|
+
assert.equal(duplicateRestore.state, 'restored');
|
|
185
229
|
|
|
186
230
|
const [_, err] = await catchError(() =>
|
|
187
231
|
app.scope('commerce-trade').service.stockBalance.consume({
|
|
@@ -190,71 +234,227 @@ describe('stockReservation.test.ts', () => {
|
|
|
190
234
|
}),
|
|
191
235
|
);
|
|
192
236
|
assert.equal(err?.code, 409);
|
|
193
|
-
const balance = await app
|
|
237
|
+
const balance = await app
|
|
238
|
+
.scope('commerce-trade')
|
|
239
|
+
.model.stockBalance.get({ skuId: fixture.skuId });
|
|
194
240
|
assert.deepEqual([balance?.onHand, balance?.reserved, balance?.available], [3, 0, 3]);
|
|
241
|
+
const audits = await app.scope('commerce-trade').model.stockAudit.select({
|
|
242
|
+
where: { skuId: fixture.skuId },
|
|
243
|
+
});
|
|
244
|
+
assert.equal(audits.length, 6);
|
|
245
|
+
assert.deepEqual(
|
|
246
|
+
audits.map(audit => audit.operation),
|
|
247
|
+
['adjust', 'reserve', 'release', 'reserve', 'consume', 'restore'],
|
|
248
|
+
);
|
|
249
|
+
assert.deepEqual(
|
|
250
|
+
audits.map(audit => audit.stockBalanceId),
|
|
251
|
+
audits.map(() => balance?.id),
|
|
252
|
+
);
|
|
253
|
+
assert.deepEqual(
|
|
254
|
+
audits.map(audit => audit.stockReservationId),
|
|
255
|
+
[
|
|
256
|
+
null,
|
|
257
|
+
releasedReservation.id,
|
|
258
|
+
releasedReservation.id,
|
|
259
|
+
consumedReservation.id,
|
|
260
|
+
consumedReservation.id,
|
|
261
|
+
consumedReservation.id,
|
|
262
|
+
],
|
|
263
|
+
);
|
|
264
|
+
assert.deepEqual(
|
|
265
|
+
audits.map(audit => audit.correlationId),
|
|
266
|
+
[
|
|
267
|
+
`reservation-setup-${suffix}`,
|
|
268
|
+
`reservation-${suffix}-release`,
|
|
269
|
+
`reservation-${suffix}-release`,
|
|
270
|
+
`reservation-${suffix}-restore`,
|
|
271
|
+
`reservation-${suffix}-restore`,
|
|
272
|
+
`reservation-${suffix}-restore`,
|
|
273
|
+
],
|
|
274
|
+
);
|
|
275
|
+
assert.deepEqual(
|
|
276
|
+
audits.map(audit => audit.reason),
|
|
277
|
+
[
|
|
278
|
+
'reservation test setup',
|
|
279
|
+
'reservation test',
|
|
280
|
+
'payment failed',
|
|
281
|
+
'reservation test',
|
|
282
|
+
'payment success',
|
|
283
|
+
'refund success',
|
|
284
|
+
],
|
|
285
|
+
);
|
|
286
|
+
assert.deepEqual(
|
|
287
|
+
audits.map(audit => [
|
|
288
|
+
audit.delta,
|
|
289
|
+
audit.priorOnHand,
|
|
290
|
+
audit.priorReserved,
|
|
291
|
+
audit.priorAvailable,
|
|
292
|
+
audit.onHand,
|
|
293
|
+
audit.reserved,
|
|
294
|
+
audit.available,
|
|
295
|
+
]),
|
|
296
|
+
[
|
|
297
|
+
[3, 0, 0, 0, 3, 0, 3],
|
|
298
|
+
[-2, 3, 0, 3, 3, 2, 1],
|
|
299
|
+
[0, 3, 2, 1, 3, 0, 3],
|
|
300
|
+
[-1, 3, 0, 3, 3, 1, 2],
|
|
301
|
+
[-1, 3, 1, 2, 2, 0, 2],
|
|
302
|
+
[1, 2, 0, 2, 3, 0, 3],
|
|
303
|
+
],
|
|
304
|
+
);
|
|
195
305
|
} finally {
|
|
306
|
+
await dropStockFixtures(fixtures);
|
|
196
307
|
await app.bean.passport.signout();
|
|
197
308
|
}
|
|
198
309
|
});
|
|
199
310
|
});
|
|
200
311
|
|
|
201
|
-
it('allows exactly one
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
312
|
+
it('allows exactly one independent reservation for the final unit', async t => {
|
|
313
|
+
if (process.env.DATABASE_DEFAULT_CLIENT !== 'pg') {
|
|
314
|
+
t.skip('requires PostgreSQL row-lock contention');
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const fixtures: IStockFixturePartial[] = [];
|
|
318
|
+
const suffix = createTestId();
|
|
319
|
+
let fixture!: IStockFixture;
|
|
320
|
+
try {
|
|
321
|
+
await app.bean.executor.mockCtx(async () => {
|
|
322
|
+
await app.bean.passport.signinMock();
|
|
323
|
+
try {
|
|
324
|
+
fixture = await prepareStock(suffix, fixtures, 1);
|
|
325
|
+
} finally {
|
|
326
|
+
await app.bean.passport.signout();
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const reserveInContext = async (correlationSuffix: string) => {
|
|
331
|
+
return await app.bean.executor.mockCtx(async () => {
|
|
332
|
+
await app.bean.passport.signinMock();
|
|
333
|
+
try {
|
|
334
|
+
return await reserve(fixture.skuId, correlationSuffix, 1);
|
|
335
|
+
} finally {
|
|
336
|
+
await app.bean.passport.signout();
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
const results = await Promise.allSettled([
|
|
341
|
+
reserveInContext(`${suffix}-first`),
|
|
342
|
+
reserveInContext(`${suffix}-second`),
|
|
343
|
+
]);
|
|
344
|
+
assert.equal(
|
|
345
|
+
results.filter(result => result.status === 'fulfilled').length,
|
|
346
|
+
1,
|
|
347
|
+
JSON.stringify(results),
|
|
348
|
+
);
|
|
349
|
+
assert.equal(
|
|
350
|
+
results.filter(result => result.status === 'rejected').length,
|
|
351
|
+
1,
|
|
352
|
+
JSON.stringify(results),
|
|
353
|
+
);
|
|
354
|
+
const rejected = results.find(result => result.status === 'rejected');
|
|
355
|
+
assert.equal(
|
|
356
|
+
(rejected as PromiseRejectedResult | undefined)?.reason?.code,
|
|
357
|
+
409,
|
|
358
|
+
String((rejected as PromiseRejectedResult | undefined)?.reason),
|
|
359
|
+
);
|
|
213
360
|
|
|
214
|
-
|
|
361
|
+
await app.bean.executor.mockCtx(async () => {
|
|
362
|
+
const balance = await app
|
|
363
|
+
.scope('commerce-trade')
|
|
364
|
+
.model.stockBalance.get({ skuId: fixture.skuId });
|
|
215
365
|
assert.deepEqual([balance?.onHand, balance?.reserved, balance?.available], [1, 1, 0]);
|
|
216
366
|
const reservations = await app.scope('commerce-trade').model.stockReservation.select({
|
|
217
|
-
where: { skuId },
|
|
367
|
+
where: { skuId: fixture.skuId },
|
|
368
|
+
});
|
|
369
|
+
const audits = await app.scope('commerce-trade').model.stockAudit.select({
|
|
370
|
+
where: { skuId: fixture.skuId },
|
|
218
371
|
});
|
|
219
|
-
const audits = await app
|
|
220
|
-
.scope('commerce-trade')
|
|
221
|
-
.model.stockAudit.select({ where: { skuId } });
|
|
222
372
|
assert.equal(reservations.length, 1);
|
|
223
373
|
assert.deepEqual(
|
|
224
374
|
audits.map(audit => audit.operation),
|
|
225
375
|
['adjust', 'reserve'],
|
|
226
376
|
);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
377
|
+
assert.equal(audits.filter(audit => audit.operation === 'reserve').length, 1);
|
|
378
|
+
assert.equal(
|
|
379
|
+
audits.some(audit => audit.correlationId === `reservation-${suffix}-first`) ||
|
|
380
|
+
audits.some(audit => audit.correlationId === `reservation-${suffix}-second`),
|
|
381
|
+
true,
|
|
382
|
+
);
|
|
383
|
+
});
|
|
384
|
+
} finally {
|
|
385
|
+
await app.bean.executor.mockCtx(async () => {
|
|
386
|
+
await dropStockFixtures(fixtures);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
231
389
|
});
|
|
232
390
|
|
|
233
|
-
it('rolls back reservation
|
|
391
|
+
it('rolls back reservation, balance, and audit writes in one transaction', async () => {
|
|
234
392
|
await app.bean.executor.mockCtx(async () => {
|
|
235
|
-
const
|
|
393
|
+
const fixtures: IStockFixturePartial[] = [];
|
|
236
394
|
await app.bean.passport.signinMock();
|
|
237
395
|
try {
|
|
238
|
-
const
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
396
|
+
const suffix = createTestId();
|
|
397
|
+
const fixture = await prepareStock(suffix, fixtures);
|
|
398
|
+
const balance = await app.scope('commerce-trade').model.stockBalance.get({
|
|
399
|
+
skuId: fixture.skuId,
|
|
400
|
+
});
|
|
401
|
+
const db = app.ctx.db;
|
|
402
|
+
const [_, err] = await catchError(async () => {
|
|
403
|
+
await db.transaction.begin(async () => {
|
|
404
|
+
const modelStockBalance = app
|
|
405
|
+
.scope('commerce-trade')
|
|
406
|
+
.model.stockBalance.newInstance(db);
|
|
407
|
+
const modelStockReservation = app
|
|
408
|
+
.scope('commerce-trade')
|
|
409
|
+
.model.stockReservation.newInstance(db);
|
|
410
|
+
const modelStockAudit = app.scope('commerce-trade').model.stockAudit.newInstance(db);
|
|
411
|
+
const reservation = await modelStockReservation.insert({
|
|
412
|
+
stockBalanceId: balance!.id,
|
|
413
|
+
skuId: fixture.skuId,
|
|
414
|
+
quantity: 2,
|
|
415
|
+
state: 'reserved',
|
|
416
|
+
correlationId: `reservation-${suffix}-rollback`,
|
|
417
|
+
});
|
|
418
|
+
await modelStockBalance.updateById(balance!.id, {
|
|
419
|
+
onHand: 3,
|
|
420
|
+
reserved: 2,
|
|
421
|
+
available: 1,
|
|
422
|
+
});
|
|
423
|
+
await modelStockAudit.insert({
|
|
424
|
+
stockBalanceId: balance!.id,
|
|
425
|
+
skuId: fixture.skuId,
|
|
426
|
+
stockReservationId: reservation.id,
|
|
427
|
+
actorId: app.bean.passport.currentUser!.id,
|
|
428
|
+
operation: 'reserve',
|
|
429
|
+
delta: -2,
|
|
430
|
+
reason: 'transaction rollback proof',
|
|
431
|
+
correlationId: reservation.correlationId,
|
|
432
|
+
priorOnHand: 3,
|
|
433
|
+
priorReserved: 0,
|
|
434
|
+
priorAvailable: 3,
|
|
435
|
+
onHand: 3,
|
|
436
|
+
reserved: 2,
|
|
437
|
+
available: 1,
|
|
438
|
+
});
|
|
439
|
+
throw new Error('transaction rollback proof');
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
assert.match(err?.message ?? '', /transaction rollback proof/);
|
|
250
443
|
|
|
251
|
-
const
|
|
252
|
-
|
|
444
|
+
const unchanged = await app
|
|
445
|
+
.scope('commerce-trade')
|
|
446
|
+
.model.stockBalance.get({ skuId: fixture.skuId });
|
|
447
|
+
assert.deepEqual([unchanged?.onHand, unchanged?.reserved, unchanged?.available], [3, 0, 3]);
|
|
253
448
|
assert.equal(
|
|
254
|
-
await app.scope('commerce-trade').model.stockReservation.get({ skuId }),
|
|
449
|
+
await app.scope('commerce-trade').model.stockReservation.get({ skuId: fixture.skuId }),
|
|
255
450
|
undefined,
|
|
256
451
|
);
|
|
452
|
+
const audits = await app.scope('commerce-trade').model.stockAudit.select({
|
|
453
|
+
where: { skuId: fixture.skuId },
|
|
454
|
+
});
|
|
455
|
+
assert.equal(audits.length, 1);
|
|
257
456
|
} finally {
|
|
457
|
+
await dropStockFixtures(fixtures);
|
|
258
458
|
await app.bean.passport.signout();
|
|
259
459
|
}
|
|
260
460
|
});
|
package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts
CHANGED
|
@@ -162,7 +162,7 @@ export class ServiceImageCloudflare extends BeanBase {
|
|
|
162
162
|
options: IImageProviderCloudflareClientOptions,
|
|
163
163
|
) {
|
|
164
164
|
const normalized = this._normalizeClientOptions(options);
|
|
165
|
-
const response = await fetch(
|
|
165
|
+
const response = await this.bean.core.fetch(
|
|
166
166
|
`${normalized.apiBaseUrl}/accounts/${normalized.accountId}/images/v1/${encodeURIComponent(image.resourceId)}`,
|
|
167
167
|
{
|
|
168
168
|
method: 'DELETE',
|
|
@@ -247,13 +247,16 @@ export class ServiceImageCloudflare extends BeanBase {
|
|
|
247
247
|
apiPath: string,
|
|
248
248
|
init: RequestInit,
|
|
249
249
|
) {
|
|
250
|
-
const response = await fetch(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
...
|
|
254
|
-
|
|
250
|
+
const response = await this.bean.core.fetch(
|
|
251
|
+
`${options.apiBaseUrl}/accounts/${options.accountId}${apiPath}`,
|
|
252
|
+
{
|
|
253
|
+
...init,
|
|
254
|
+
headers: {
|
|
255
|
+
...this._createHeaders(options),
|
|
256
|
+
...(init.headers ?? {}),
|
|
257
|
+
},
|
|
255
258
|
},
|
|
256
|
-
|
|
259
|
+
);
|
|
257
260
|
return await this._parseEnvelope<T>(response);
|
|
258
261
|
}
|
|
259
262
|
|
|
@@ -15,15 +15,28 @@ export interface IUseAopMethodPropMetadataInner<T extends keyof IAopMethodRecord
|
|
|
15
15
|
|
|
16
16
|
export interface IAopMethodRecord {}
|
|
17
17
|
|
|
18
|
+
export type AopMethodNext = Next & {
|
|
19
|
+
replay: Next;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type AopMethodNextSync = NextSync & {
|
|
23
|
+
replay: NextSync;
|
|
24
|
+
};
|
|
25
|
+
|
|
18
26
|
export interface IAopMethodGet {
|
|
19
|
-
get(
|
|
27
|
+
get(
|
|
28
|
+
options: IDecoratorAopMethodOptions,
|
|
29
|
+
next: AopMethodNextSync,
|
|
30
|
+
receiver: any,
|
|
31
|
+
prop: string,
|
|
32
|
+
): any;
|
|
20
33
|
}
|
|
21
34
|
|
|
22
35
|
export interface IAopMethodSet {
|
|
23
36
|
set(
|
|
24
37
|
options: IDecoratorAopMethodOptions,
|
|
25
38
|
value: any,
|
|
26
|
-
next:
|
|
39
|
+
next: AopMethodNextSync,
|
|
27
40
|
receiver: any,
|
|
28
41
|
prop: string,
|
|
29
42
|
): boolean;
|
|
@@ -33,7 +46,7 @@ export interface IAopMethodExecute {
|
|
|
33
46
|
execute(
|
|
34
47
|
options: IDecoratorAopMethodOptions,
|
|
35
48
|
args: [],
|
|
36
|
-
next:
|
|
49
|
+
next: AopMethodNext | AopMethodNextSync,
|
|
37
50
|
receiver: any,
|
|
38
51
|
prop: string,
|
|
39
52
|
): Promise<any> | any;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona-module-a-core",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.45",
|
|
4
4
|
"gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
|
|
5
5
|
"description": "",
|
|
6
6
|
"keywords": [
|
|
@@ -84,7 +84,9 @@
|
|
|
84
84
|
},
|
|
85
85
|
"title": "a-core",
|
|
86
86
|
"vonaModule": {
|
|
87
|
-
"dependencies": {
|
|
87
|
+
"dependencies": {
|
|
88
|
+
"a-retryable": "5.0.0"
|
|
89
|
+
},
|
|
88
90
|
"globalDependencies": {
|
|
89
91
|
"@babel/core": true,
|
|
90
92
|
"@babel/traverse": true,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { BeanBase } from 'vona';
|
|
2
2
|
import { Bean } from 'vona-module-a-bean';
|
|
3
3
|
|
|
4
|
+
import type { TypeContextFetch } from '../types/fetch.ts';
|
|
5
|
+
|
|
4
6
|
@Bean()
|
|
5
7
|
export class BeanCore extends BeanBase {
|
|
6
8
|
get protocol() {
|
|
@@ -11,6 +13,10 @@ export class BeanCore extends BeanBase {
|
|
|
11
13
|
return this.app.util.host;
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
get fetch(): TypeContextFetch {
|
|
17
|
+
return this.ctx.state.fetch ?? globalThis.fetch;
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
getAbsoluteUrl(path?: string) {
|
|
15
21
|
return this.app.util.getAbsoluteUrl(path);
|
|
16
22
|
}
|
|
@@ -3,6 +3,7 @@ import type { IFilterOptionsError } from 'vona-module-a-error';
|
|
|
3
3
|
import type { IAopMethodOptionsLog } from 'vona-module-a-logger';
|
|
4
4
|
import type { TypeUseOnionOmitOptionsGlobal } from 'vona-module-a-onion';
|
|
5
5
|
import type { IAopMethodOptionsTransaction } from 'vona-module-a-orm';
|
|
6
|
+
import type { IAopMethodOptionsRetryable } from 'vona-module-a-retryable';
|
|
6
7
|
import type { IInterceptorOptionsUpload } from 'vona-module-a-upload';
|
|
7
8
|
|
|
8
9
|
import { Aspect } from 'vona-module-a-aspect';
|
|
@@ -29,6 +30,10 @@ function Transaction(options?: Partial<IAopMethodOptionsTransaction>): MethodDec
|
|
|
29
30
|
return Aspect.aopMethod('a-orm:transaction', options);
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
function Retryable(options: IAopMethodOptionsRetryable): MethodDecorator {
|
|
34
|
+
return Aspect.aopMethod('a-retryable:retryable', options);
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
function CaptchaVerify(options?: Partial<IInterceptorOptionsCaptchaVerify>): MethodDecorator {
|
|
33
38
|
return Aspect.interceptor('a-captcha:captchaVerify', options);
|
|
34
39
|
}
|
|
@@ -46,6 +51,7 @@ export const Core = {
|
|
|
46
51
|
error: Error,
|
|
47
52
|
log: Log,
|
|
48
53
|
transaction: Transaction,
|
|
54
|
+
retryable: Retryable,
|
|
49
55
|
captchaVerify: CaptchaVerify,
|
|
50
56
|
fileUpload: FileUpload,
|
|
51
57
|
serializer: Serializer,
|