@tbox.cn/app-contracts-mall 0.2.0 → 0.10.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/README.md +16 -2
- package/dist/alipay-activity/cards.d.ts +305 -0
- package/dist/assembly.d.ts +73 -7
- package/dist/chunk-KIZD6FZ2.js +43 -0
- package/dist/deployment.d.ts +32 -33
- package/dist/domain/activity.d.ts +64 -0
- package/dist/domain/coupon.d.ts +2 -0
- package/dist/domain/entitlement.d.ts +53 -0
- package/dist/domain/offer.d.ts +111 -0
- package/dist/domain/operation.d.ts +22 -0
- package/dist/domain/parking.d.ts +195 -12
- package/dist/domain/points.d.ts +3 -2
- package/dist/domain/shopping-guide.d.ts +201 -0
- package/dist/index.d.ts +15 -2
- package/dist/mall-scope-url.d.ts +13 -0
- package/dist/member/cards.d.ts +636 -213
- package/dist/parking/cards.d.ts +1271 -0
- package/dist/promotion/cards.d.ts +2529 -0
- package/dist/query-context.d.ts +47 -0
- package/dist/runtime.d.ts +12 -2
- package/dist/runtime.js +940 -86
- package/dist/scope.d.ts +42 -15
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.js +92 -0
- package/dist/server/mall-context.d.ts +24 -0
- package/dist/server/mall-control-plane.d.ts +10 -0
- package/dist/server/request-credentials.d.ts +22 -0
- package/dist/services/customer-service.d.ts +11 -0
- package/dist/services/mall-info.d.ts +39 -0
- package/dist/services/member.d.ts +188 -31
- package/dist/services/parking.d.ts +115 -11
- package/dist/services/promotion.d.ts +100 -4
- package/dist/services/shopping-guide.d.ts +97 -0
- package/dist/shared/cards.d.ts +42 -0
- package/dist/shopping-guide/cards.d.ts +3399 -561
- package/package.json +19 -7
- package/src/alipay-activity/cards.ts +66 -0
- package/src/assembly.ts +159 -0
- package/src/deployment.ts +44 -0
- package/src/domain/activity.ts +66 -0
- package/src/domain/coupon.ts +11 -0
- package/src/domain/discount.ts +6 -0
- package/src/domain/entitlement.ts +56 -0
- package/src/domain/offer.ts +120 -0
- package/src/domain/operation.ts +40 -0
- package/src/domain/parking.ts +207 -0
- package/src/domain/points.ts +15 -0
- package/src/domain/shopping-guide.ts +242 -0
- package/src/events/index.ts +10 -0
- package/src/guard.ts +48 -0
- package/src/index.ts +34 -0
- package/src/mall-scope-url.ts +21 -0
- package/src/member/cards.ts +209 -0
- package/src/parking/cards.ts +233 -0
- package/src/promotion/cards.ts +290 -0
- package/src/query-context.ts +81 -0
- package/src/runtime.ts +43 -0
- package/src/scope.ts +92 -0
- package/src/server/index.ts +11 -0
- package/src/server/mall-context.ts +39 -0
- package/src/server/mall-control-plane.ts +92 -0
- package/src/server/request-credentials.ts +43 -0
- package/src/service-slots.json +26 -0
- package/src/services/customer-service.ts +12 -0
- package/src/services/mall-info.ts +48 -0
- package/src/services/member.ts +386 -0
- package/src/services/parking.ts +182 -0
- package/src/services/promotion.ts +126 -0
- package/src/services/shopping-guide.ts +138 -0
- package/src/shared/cards.ts +25 -0
- package/src/shopping-guide/cards.ts +438 -0
- package/tests/assembly-validate-scope.test.ts +132 -0
- package/tests/cards.test.ts +586 -0
- package/tests/context-unification.test.ts +73 -0
- package/tests/mall-control-plane.test.ts +169 -0
- package/tests/mall-scope-url.test.ts +59 -0
- package/tests/member-status.test.ts +197 -0
- package/tests/query-context.test.ts +111 -0
- package/tests/request-credentials.test.ts +38 -0
- package/tests/resolve-primitives.test.ts +55 -0
- package/tests/service-slots.test.ts +83 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +5 -0
- package/dist/shopping-guide/ports.d.ts +0 -216
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 营销域卡片数据契约。
|
|
3
|
+
* 传输层维持 9 个稳定 CardType;presentation 显式保留 legacy marketing 的 19 个业务展示语义。
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { actionDraftSchema } from '../shared/cards';
|
|
7
|
+
|
|
8
|
+
/** 券/活动封面(厂商 CDN,可缺省) */
|
|
9
|
+
const imageUrlSchema = z.string().max(2048).optional();
|
|
10
|
+
const freeCouponClaimModeSchema = z.enum(['DIRECT', 'REDIRECT']);
|
|
11
|
+
|
|
12
|
+
/** Offer 摘要(列表条目) */
|
|
13
|
+
const offerSummarySchema = z.object({
|
|
14
|
+
offerRef: z.string().min(1),
|
|
15
|
+
title: z.string().min(1),
|
|
16
|
+
subtitle: z.string().optional(),
|
|
17
|
+
offerType: z.enum(['COUPON', 'BUNDLE', 'PROMOTION', 'ACTIVITY', 'GROUPON']),
|
|
18
|
+
/** 主 facet 一句话(如「满 100 减 20」) */
|
|
19
|
+
benefitText: z.string().optional(),
|
|
20
|
+
/** 使用门槛(如「满 300 元可用」) */
|
|
21
|
+
thresholdText: z.string().optional(),
|
|
22
|
+
/**
|
|
23
|
+
* 券的**售价**展示,与 benefitText 是两回事——后者说优惠内容,这里说买这张券要花多少。
|
|
24
|
+
* 免费券给「免费」,付费券给「¥28.8」。文案由 provider 直接给:各厂商币种、小数位与
|
|
25
|
+
* 「免费/0 元」措辞不一,客户端不做数值运算也不反解金额。
|
|
26
|
+
*/
|
|
27
|
+
priceText: z.string().optional(),
|
|
28
|
+
/** 划线原价(如「¥38.8」)。与 priceText 成对出现才有意义,单独给不渲染。 */
|
|
29
|
+
originalPriceText: z.string().optional(),
|
|
30
|
+
/** 折扣角标(如「7.5折」)。同样是 provider 直出文案,不由前端拿两个价格算。 */
|
|
31
|
+
discountLabel: z.string().optional(),
|
|
32
|
+
/**
|
|
33
|
+
* 领取按钮文案:免费券「领取」、付费券「抢购」。由 provider 给而非前端按 priceText
|
|
34
|
+
* 猜——「免费」的写法各家不同,猜错就把付费券写成领取。缺省回落「领取」。
|
|
35
|
+
*/
|
|
36
|
+
claimActionLabel: z.string().optional(),
|
|
37
|
+
purchaseUrl: z.string().max(2048).optional(),
|
|
38
|
+
freeCouponClaimMode: freeCouponClaimModeSchema.optional(),
|
|
39
|
+
/**
|
|
40
|
+
* 下面三条供**列表内的详情浮层**渲染——浮层就地展开,没有二次请求可拉,
|
|
41
|
+
* 需要的信息必须随列表一起下发。厂商不给就不渲染对应区块。
|
|
42
|
+
*/
|
|
43
|
+
stockText: z.string().optional(),
|
|
44
|
+
usageNoticeText: z.string().optional(),
|
|
45
|
+
guaranteeText: z.string().optional(),
|
|
46
|
+
usageSceneText: z.string().optional(),
|
|
47
|
+
usageTimeText: z.string().optional(),
|
|
48
|
+
claimTimeText: z.string().optional(),
|
|
49
|
+
usageRuleText: z.string().optional(),
|
|
50
|
+
/** 可领取(availability.status === 'AVAILABLE') */
|
|
51
|
+
claimable: z.boolean(),
|
|
52
|
+
availabilityStatus: z
|
|
53
|
+
.enum(['AVAILABLE', 'UNAVAILABLE', 'REQUIRES_ACTION', 'UNKNOWN'])
|
|
54
|
+
.optional(),
|
|
55
|
+
validityText: z.string().optional(),
|
|
56
|
+
imageUrl: imageUrlSchema,
|
|
57
|
+
/** 0.3.2 D4:条目动作(send:查看详情) */
|
|
58
|
+
itemActions: z.array(actionDraftSchema).optional(),
|
|
59
|
+
tags: z.array(z.string()),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/** promotion-offer-list:搜索/可领/推荐 三态列表 */
|
|
63
|
+
export const promotionOfferListCardDataSchema = z.object({
|
|
64
|
+
presentation: z.enum(['offer', 'promotion', 'coupon', 'recommendation']).optional(),
|
|
65
|
+
mode: z.enum(['search', 'claimable', 'recommend']),
|
|
66
|
+
/** 客户端默认展示 4 条;传入正整数可覆盖该上限。 */
|
|
67
|
+
maxItems: z.number().int().positive().optional(),
|
|
68
|
+
summary: z.string().optional(),
|
|
69
|
+
items: z.array(offerSummarySchema),
|
|
70
|
+
noResultsReason: z.string().optional(),
|
|
71
|
+
nextCursor: z.string().optional(),
|
|
72
|
+
});
|
|
73
|
+
export type PromotionOfferListCardData = z.infer<typeof promotionOfferListCardDataSchema>;
|
|
74
|
+
|
|
75
|
+
/** promotion-offer-detail:详情 + 领取/券码动作 */
|
|
76
|
+
export const promotionOfferDetailCardDataSchema = z.object({
|
|
77
|
+
presentation: z
|
|
78
|
+
.enum(['offer', 'promotion', 'promotion-guide', 'coupon', 'package'])
|
|
79
|
+
.optional(),
|
|
80
|
+
offerRef: z.string().min(1),
|
|
81
|
+
title: z.string().min(1),
|
|
82
|
+
offerType: z.enum(['COUPON', 'BUNDLE', 'PROMOTION', 'ACTIVITY', 'GROUPON']),
|
|
83
|
+
subtitle: z.string().optional(),
|
|
84
|
+
description: z.string().optional(),
|
|
85
|
+
imageUrl: imageUrlSchema,
|
|
86
|
+
benefitText: z.string().optional(),
|
|
87
|
+
thresholdText: z.string().optional(),
|
|
88
|
+
validityText: z.string().optional(),
|
|
89
|
+
/** 售价三件套,语义同 offerSummary(provider 直出文案,前端不做运算) */
|
|
90
|
+
priceText: z.string().optional(),
|
|
91
|
+
originalPriceText: z.string().optional(),
|
|
92
|
+
discountLabel: z.string().optional(),
|
|
93
|
+
claimActionLabel: z.string().optional(),
|
|
94
|
+
purchaseUrl: z.string().max(2048).optional(),
|
|
95
|
+
/** 剩余库存文案(如「剩余:29」);厂商不给就不展示,别拿 0 当「售罄」用 */
|
|
96
|
+
stockText: z.string().optional(),
|
|
97
|
+
usageNoticeText: z.string().optional(),
|
|
98
|
+
/** 核销场景(如「万铺小二核销」)——告诉用户这张券在哪、怎么用掉 */
|
|
99
|
+
usageSceneText: z.string().optional(),
|
|
100
|
+
usageTimeText: z.string().optional(),
|
|
101
|
+
claimTimeText: z.string().optional(),
|
|
102
|
+
usageRuleText: z.string().optional(),
|
|
103
|
+
guaranteeText: z.string().optional(),
|
|
104
|
+
/** 使用规则/参与步骤 */
|
|
105
|
+
rules: z.array(z.string()),
|
|
106
|
+
participationMode: z
|
|
107
|
+
.enum([
|
|
108
|
+
'AUTOMATIC_DISCOUNT',
|
|
109
|
+
'CLAIM_THEN_PURCHASE',
|
|
110
|
+
'REGISTER_THEN_ATTEND',
|
|
111
|
+
'GROUP_PURCHASE',
|
|
112
|
+
'UNKNOWN',
|
|
113
|
+
])
|
|
114
|
+
.optional(),
|
|
115
|
+
steps: z
|
|
116
|
+
.array(z.object({ title: z.string(), description: z.string().optional() }))
|
|
117
|
+
.optional(),
|
|
118
|
+
requirements: z.array(z.string()).optional(),
|
|
119
|
+
notice: z.string().optional(),
|
|
120
|
+
/** 厂商可提供时展示适用门店;可选以兼容历史卡数据。 */
|
|
121
|
+
stores: z.array(z.object({
|
|
122
|
+
storeId: z.string().min(1),
|
|
123
|
+
storeName: z.string().min(1),
|
|
124
|
+
businessName: z.string().optional(),
|
|
125
|
+
address: z.string().optional(),
|
|
126
|
+
floor: z.string().optional(),
|
|
127
|
+
})).optional(),
|
|
128
|
+
bundleItems: z
|
|
129
|
+
.array(z.object({ name: z.string(), quantity: z.number().int().positive() }))
|
|
130
|
+
.optional(),
|
|
131
|
+
/** 领取动作是否可用(未登录会员/不可用 → false + unavailableReason) */
|
|
132
|
+
claimable: z.boolean(),
|
|
133
|
+
unavailableReason: z.string().optional(),
|
|
134
|
+
caveats: z.array(z.string()),
|
|
135
|
+
});
|
|
136
|
+
export type PromotionOfferDetailCardData = z.infer<typeof promotionOfferDetailCardDataSchema>;
|
|
137
|
+
|
|
138
|
+
/** promotion-entitlement-list:我的券 */
|
|
139
|
+
export const promotionEntitlementListCardDataSchema = z.object({
|
|
140
|
+
mallId: z.string().optional(),
|
|
141
|
+
miniAppId: z.string().optional(),
|
|
142
|
+
/** 客户端默认展示 4 条;传入正整数可覆盖该上限。 */
|
|
143
|
+
maxItems: z.number().int().positive().optional(),
|
|
144
|
+
items: z.array(
|
|
145
|
+
z.object({
|
|
146
|
+
entitlementRef: z.string().min(1),
|
|
147
|
+
title: z.string().min(1),
|
|
148
|
+
imageUrl: imageUrlSchema,
|
|
149
|
+
entitlementType: z.enum(['COUPON', 'BUNDLE', 'PROMOTION', 'ACTIVITY', 'GROUPON']),
|
|
150
|
+
status: z.enum(['EFFECTIVE', 'USED', 'EXPIRED', 'INVALID', 'UNKNOWN']),
|
|
151
|
+
codeMasked: z.string().optional(),
|
|
152
|
+
code: z.string().optional(),
|
|
153
|
+
qrCodeEnabled: z.boolean().optional(),
|
|
154
|
+
benefitText: z.string().optional(),
|
|
155
|
+
thresholdText: z.string().optional(),
|
|
156
|
+
usageNotice: z.string().optional(),
|
|
157
|
+
guaranteeText: z.string().optional(),
|
|
158
|
+
usageSceneText: z.string().optional(),
|
|
159
|
+
usageTimeText: z.string().optional(),
|
|
160
|
+
claimTimeText: z.string().optional(),
|
|
161
|
+
usageRuleText: z.string().optional(),
|
|
162
|
+
validityText: z.string().optional(),
|
|
163
|
+
validUntilText: z.string().optional(),
|
|
164
|
+
/** 0.3.2 D4:条目动作(send:查看详情) */
|
|
165
|
+
itemActions: z.array(actionDraftSchema).optional(),
|
|
166
|
+
stores: z.array(z.object({
|
|
167
|
+
storeId: z.string(),
|
|
168
|
+
storeName: z.string(),
|
|
169
|
+
businessName: z.string().optional(),
|
|
170
|
+
imageUrl: imageUrlSchema,
|
|
171
|
+
address: z.string().optional(),
|
|
172
|
+
floor: z.string().optional(),
|
|
173
|
+
latitude: z.number().optional(),
|
|
174
|
+
longitude: z.number().optional(),
|
|
175
|
+
})).optional(),
|
|
176
|
+
}),
|
|
177
|
+
),
|
|
178
|
+
noResultsReason: z.string().optional(),
|
|
179
|
+
nextCursor: z.string().optional(),
|
|
180
|
+
});
|
|
181
|
+
export type PromotionEntitlementListCardData = z.infer<typeof promotionEntitlementListCardDataSchema>;
|
|
182
|
+
|
|
183
|
+
/** promotion-activity-category-list:活动分类入口。 */
|
|
184
|
+
export const promotionActivityCategoryListCardDataSchema = z.object({
|
|
185
|
+
items: z.array(
|
|
186
|
+
z.object({
|
|
187
|
+
categoryId: z.string().min(1),
|
|
188
|
+
name: z.string().min(1),
|
|
189
|
+
parentId: z.string().optional(),
|
|
190
|
+
itemActions: z.array(actionDraftSchema).optional(),
|
|
191
|
+
}),
|
|
192
|
+
),
|
|
193
|
+
noResultsReason: z.string().optional(),
|
|
194
|
+
});
|
|
195
|
+
export type PromotionActivityCategoryListCardData = z.infer<
|
|
196
|
+
typeof promotionActivityCategoryListCardDataSchema
|
|
197
|
+
>;
|
|
198
|
+
|
|
199
|
+
/** promotion-best-deal:确定性测算或厂商不支持时的候选说明。 */
|
|
200
|
+
export const promotionBestDealCardDataSchema = z.object({
|
|
201
|
+
authoritative: z.boolean(),
|
|
202
|
+
originalAmountCents: z.number().int().min(0),
|
|
203
|
+
savingsCents: z.number().int().min(0).optional(),
|
|
204
|
+
expectedPayCents: z.number().int().min(0).optional(),
|
|
205
|
+
appliedOfferRefs: z.array(z.string()),
|
|
206
|
+
candidates: z.array(offerSummarySchema).optional(),
|
|
207
|
+
message: z.string().optional(),
|
|
208
|
+
});
|
|
209
|
+
export type PromotionBestDealCardData = z.infer<typeof promotionBestDealCardDataSchema>;
|
|
210
|
+
|
|
211
|
+
/** promotion-activity-list */
|
|
212
|
+
export const promotionActivityListCardDataSchema = z.object({
|
|
213
|
+
items: z.array(
|
|
214
|
+
z.object({
|
|
215
|
+
activityRef: z.string().min(1),
|
|
216
|
+
title: z.string().min(1),
|
|
217
|
+
subtitle: z.string().optional(),
|
|
218
|
+
coverUrl: imageUrlSchema,
|
|
219
|
+
registrationRequired: z.boolean(),
|
|
220
|
+
jumpOnly: z.boolean().optional(),
|
|
221
|
+
jumpLabel: z.string().min(1).optional(),
|
|
222
|
+
remainingQuota: z.number().int().min(0).optional(),
|
|
223
|
+
periodText: z.string().optional(),
|
|
224
|
+
categoryName: z.string().optional(),
|
|
225
|
+
availabilityStatus: z
|
|
226
|
+
.enum(['AVAILABLE', 'FULL', 'ENDED', 'UPCOMING', 'UNKNOWN'])
|
|
227
|
+
.optional(),
|
|
228
|
+
itemActions: z.array(actionDraftSchema).optional(),
|
|
229
|
+
}),
|
|
230
|
+
),
|
|
231
|
+
noResultsReason: z.string().optional(),
|
|
232
|
+
nextCursor: z.string().optional(),
|
|
233
|
+
});
|
|
234
|
+
export type PromotionActivityListCardData = z.infer<typeof promotionActivityListCardDataSchema>;
|
|
235
|
+
|
|
236
|
+
/** promotion-activity-detail:详情 + 场次 + 指南 */
|
|
237
|
+
export const promotionActivityDetailCardDataSchema = z.object({
|
|
238
|
+
presentation: z.enum(['detail', 'schedule', 'guide']).optional(),
|
|
239
|
+
activityRef: z.string().min(1),
|
|
240
|
+
title: z.string().min(1),
|
|
241
|
+
subtitle: z.string().optional(),
|
|
242
|
+
categoryName: z.string().optional(),
|
|
243
|
+
description: z.string().optional(),
|
|
244
|
+
coverUrl: imageUrlSchema,
|
|
245
|
+
periodText: z.string().optional(),
|
|
246
|
+
location: z.string().optional(),
|
|
247
|
+
registrationRequired: z.boolean(),
|
|
248
|
+
jumpOnly: z.boolean().optional(),
|
|
249
|
+
jumpLabel: z.string().min(1).optional(),
|
|
250
|
+
/** 需报名时直达厂商支付宝小程序活动详情;由客户端 openLink 本地处理。 */
|
|
251
|
+
registrationUrl: z.string().max(2048).optional(),
|
|
252
|
+
remainingQuota: z.number().int().min(0).optional(),
|
|
253
|
+
sessions: z.array(
|
|
254
|
+
z.object({
|
|
255
|
+
sessionRef: z.string(),
|
|
256
|
+
title: z.string(),
|
|
257
|
+
startText: z.string(),
|
|
258
|
+
endText: z.string().optional(),
|
|
259
|
+
location: z.string().optional(),
|
|
260
|
+
remainingQuota: z.number().int().min(0).optional(),
|
|
261
|
+
status: z.enum(['OPEN', 'FULL', 'CLOSED', 'UNKNOWN']),
|
|
262
|
+
}),
|
|
263
|
+
),
|
|
264
|
+
guideSteps: z.array(z.string()),
|
|
265
|
+
guideItems: z
|
|
266
|
+
.array(z.object({ title: z.string(), description: z.string().optional() }))
|
|
267
|
+
.optional(),
|
|
268
|
+
conditions: z.array(z.string()),
|
|
269
|
+
notice: z.string().optional(),
|
|
270
|
+
});
|
|
271
|
+
export type PromotionActivityDetailCardData = z.infer<typeof promotionActivityDetailCardDataSchema>;
|
|
272
|
+
|
|
273
|
+
/** promotion-bumpin-activity card schema 已迁移至 alipay-activity/cards.ts,此处 re-export 保持向后兼容 */
|
|
274
|
+
export {
|
|
275
|
+
promotionBumpInActivityCardDataSchema,
|
|
276
|
+
type PromotionBumpInActivityCardData,
|
|
277
|
+
} from '../alipay-activity/cards';
|
|
278
|
+
|
|
279
|
+
/** 10 个稳定传输卡型;其 presentation 变体覆盖 19 个 legacy 业务展示语义。 */
|
|
280
|
+
export const promotionCardDataSchemas = {
|
|
281
|
+
'promotion-offer-list': promotionOfferListCardDataSchema,
|
|
282
|
+
'promotion-offer-detail': promotionOfferDetailCardDataSchema,
|
|
283
|
+
'promotion-entitlement-list': promotionEntitlementListCardDataSchema,
|
|
284
|
+
'promotion-activity-category-list': promotionActivityCategoryListCardDataSchema,
|
|
285
|
+
'promotion-best-deal': promotionBestDealCardDataSchema,
|
|
286
|
+
'promotion-activity-list': promotionActivityListCardDataSchema,
|
|
287
|
+
'promotion-activity-detail': promotionActivityDetailCardDataSchema,
|
|
288
|
+
} as const;
|
|
289
|
+
|
|
290
|
+
export type PromotionCardType = keyof typeof promotionCardDataSchemas;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mall 查询上下文组装单源(读/写/消费三层中的消费层 + query 写入口)。
|
|
3
|
+
*
|
|
4
|
+
* 原子 + 组合子分层:mallScopeOrEmpty / mallSubjectIdOf 为原子,toMallQueryContext 为
|
|
5
|
+
* 宽松组合子;strict 语义(member 域 throw / shopping-guide toShoppingQueryContext)
|
|
6
|
+
* 留在模块内,不在本文件。
|
|
7
|
+
*
|
|
8
|
+
* plazaId 边界(不可变约束):plazaId 归一是客户端 mall-scope-url 单点职责,
|
|
9
|
+
* 本文件只认 mallId 契约键,不接受别名(四道防护见 docs/plans/mall-scope-unification)。
|
|
10
|
+
*/
|
|
11
|
+
import type { RequestContext } from '@tbox.cn/app-contracts';
|
|
12
|
+
import { MALL_SCOPE_KEY, extractMallScope } from './scope';
|
|
13
|
+
import type { MallScope } from './scope';
|
|
14
|
+
|
|
15
|
+
/** C 端查询上下文公共形状(三域 *QueryContext extends;未来新域直接继承) */
|
|
16
|
+
export interface MallQueryContext {
|
|
17
|
+
readonly scope: MallScope;
|
|
18
|
+
readonly subjectId?: string;
|
|
19
|
+
readonly requestContext: RequestContext;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 空 mall scope 兜底(demo/单实例部署:scope 缺席时以空 mallId 命中 mock 单实例)。
|
|
24
|
+
* 函数化而非共享常量——miss 返回新对象,免疫「调用方原地改写」共享引用事故。
|
|
25
|
+
*/
|
|
26
|
+
export function mallScopeOrEmpty(reqCtx: RequestContext): MallScope {
|
|
27
|
+
return extractMallScope(reqCtx) ?? { miniAppId: '', mallId: '' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* C 端主体归一(登录态 = source !== 'anonymous',与 mall-context.ts 定义同源)。
|
|
32
|
+
* 五模块 subjectId 判式三态分裂(!=='anonymous' / 无条件 / local‖external)的归一点。
|
|
33
|
+
*/
|
|
34
|
+
export function mallSubjectIdOf(reqCtx: RequestContext): string | undefined {
|
|
35
|
+
return reqCtx.identity && reqCtx.identity.source !== 'anonymous'
|
|
36
|
+
? reqCtx.identity.userId
|
|
37
|
+
: undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* GET 路由 mall scope 供给:query 部分形状 → attributes 注入(唯一写入口)。
|
|
42
|
+
*
|
|
43
|
+
* - mallId 非空 string(trim)才注入,覆盖既有 attributes.mall(与既有 spread 覆盖语义一致);
|
|
44
|
+
* - miniAppId = query 透传优先 → opts.miniAppId 兜底(模块路由 = 卡片 data 链回传;
|
|
45
|
+
* 模板路由 = 配置面 root config/malls 表——两种真实并存的真源策略);双缺 → 注入
|
|
46
|
+
* mallId-only(v4:miniAppId 可选——URL 明确携带 mallId 却落全局会话是与恒动态
|
|
47
|
+
* 语义矛盾的倒退;parseMallScope 对 mallId-only 恒成功,旧「注入了却 parse 不了」
|
|
48
|
+
* 中间态不复存在);
|
|
49
|
+
* - 只认 mallId 契约键;plazaId 归一是客户端 mall-scope-url 单点职责,本入口不接受别名;
|
|
50
|
+
* - 安全模型同登录面:无入口白名单,registry per-service scope 绑定兜底
|
|
51
|
+
* (伪造 mallId → resolveService 预期缺席 → 各域兜底/fail-loud);
|
|
52
|
+
* - query 结构型参数 Readonly<Record<string, unknown>> 不耦合 express(ParsedQs 结构兼容)。
|
|
53
|
+
*/
|
|
54
|
+
export function withMallScopeFromQuery(
|
|
55
|
+
reqCtx: RequestContext,
|
|
56
|
+
query: Readonly<Record<string, unknown>>,
|
|
57
|
+
opts?: { miniAppId?: string },
|
|
58
|
+
): RequestContext {
|
|
59
|
+
const text = (value: unknown): string | undefined =>
|
|
60
|
+
typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
61
|
+
const mallId = text(query['mallId']);
|
|
62
|
+
if (!mallId) return reqCtx;
|
|
63
|
+
const miniAppId = text(query['miniAppId']) ?? text(opts?.miniAppId);
|
|
64
|
+
return {
|
|
65
|
+
...reqCtx,
|
|
66
|
+
attributes: {
|
|
67
|
+
...reqCtx.attributes,
|
|
68
|
+
[MALL_SCOPE_KEY]: miniAppId ? { mallId, miniAppId } : { mallId },
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 宽松组合子:空兜底 + 主体归一一键组装(零参数——无覆盖口) */
|
|
74
|
+
export function toMallQueryContext(reqCtx: RequestContext): MallQueryContext {
|
|
75
|
+
const subjectId = mallSubjectIdOf(reqCtx);
|
|
76
|
+
return {
|
|
77
|
+
scope: mallScopeOrEmpty(reqCtx),
|
|
78
|
+
...(subjectId ? { subjectId } : {}),
|
|
79
|
+
requestContext: reqCtx,
|
|
80
|
+
};
|
|
81
|
+
}
|
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @tbox.cn/app-contracts-mall runtime 入口(011 A5:types-only → types + runtime)。
|
|
3
|
+
* 本文件为唯一运行时 JS(经 tsup/esbuild 出 dist/runtime.js);
|
|
4
|
+
* 类型面仍经 tsc emitDeclarationOnly 出 dist/*.d.ts。
|
|
5
|
+
* 依赖方向:contracts-mall → contracts(TIER0);SDK 不 import 本包(011 B3)。
|
|
6
|
+
*/
|
|
7
|
+
export { MALL_SCOPE_KEY, mallScopeSchema, mallSlot, parseMallScope, extractMallScope, mallScopeOf, readLoginMallId } from './scope';
|
|
8
|
+
export { mallScopeOrEmpty, mallSubjectIdOf, withMallScopeFromQuery, toMallQueryContext } from './query-context';
|
|
9
|
+
export { createMallRuntimeAssembly, createMallContextValidator, withMallName, AUTH_LOGIN_SERVICE } from './assembly';
|
|
10
|
+
export { readMallIdFromUrl, readMiniAppIdFromUrl } from './mall-scope-url';
|
|
11
|
+
export {
|
|
12
|
+
MEMBER_ACCOUNT_SERVICE,
|
|
13
|
+
MEMBER_PROFILE_SERVICE,
|
|
14
|
+
MEMBER_CARD_SERVICE,
|
|
15
|
+
MEMBER_BENEFITS_SERVICE,
|
|
16
|
+
MEMBER_ENROLLMENT_SERVICE,
|
|
17
|
+
MEMBER_SNAPSHOT_SERVICE,
|
|
18
|
+
classifyMemberOutcome,
|
|
19
|
+
externalMemberRefOf,
|
|
20
|
+
memberGateCardOf,
|
|
21
|
+
resolveMemberGate,
|
|
22
|
+
} from './services/member';
|
|
23
|
+
export { PARKING_QUERY_SERVICE, PARKING_PAYMENT_SERVICE, PARKING_VEHICLE_SERVICE, PARKING_SNAPSHOT_SERVICE } from './services/parking';
|
|
24
|
+
export {
|
|
25
|
+
PROMOTION_OFFERS_SERVICE,
|
|
26
|
+
PROMOTION_ENTITLEMENTS_SERVICE,
|
|
27
|
+
PROMOTION_ACTIVITIES_SERVICE,
|
|
28
|
+
PROMOTION_ACQUISITION_SERVICE,
|
|
29
|
+
PROMOTION_ACTIVITY_ROUTING_POLICY,
|
|
30
|
+
} from './services/promotion';
|
|
31
|
+
export {
|
|
32
|
+
SHOPPING_CATALOG_SERVICE,
|
|
33
|
+
SHOPPING_SHOPS_SERVICE,
|
|
34
|
+
SHOPPING_CATEGORIES_SERVICE,
|
|
35
|
+
SHOPPING_DIRECTORY_SERVICE,
|
|
36
|
+
} from './services/shopping-guide';
|
|
37
|
+
export { MALL_INFO_SERVICE, MALL_INFO_CACHE_SERVICE } from './services/mall-info';
|
|
38
|
+
export { mallLocationSchema, parseMallLocation, mallUserRefSchema, parseMallUserRef } from './guard';
|
|
39
|
+
export * from './shopping-guide/cards';
|
|
40
|
+
export * from './member/cards';
|
|
41
|
+
export * from './promotion/cards';
|
|
42
|
+
export * from './parking/cards';
|
|
43
|
+
export * from './shared/cards';
|
package/src/scope.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 商圈 scope(011 B2:TIER1 领域语义唯一真源):
|
|
3
|
+
* MallScope 类型 + zod 解析 + ContextSlot 槽位 + RequestContext 提取器。
|
|
4
|
+
*
|
|
5
|
+
* 消费方(模块)只依赖本文件解析 attributes,不解析原始形状(011 B4);
|
|
6
|
+
* TIER0(app-contracts)不出现任何 mall 语义(011 B1)。
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { defineContextSlot } from '@tbox.cn/app-contracts';
|
|
10
|
+
import type { RequestContext } from '@tbox.cn/app-contracts';
|
|
11
|
+
|
|
12
|
+
/** attributes 键名(TIER0 槽位内 TIER1 自定键,单一来源) */
|
|
13
|
+
export const MALL_SCOPE_KEY = 'mall';
|
|
14
|
+
|
|
15
|
+
/** 商圈 scope(客户端输入形状——HELLO context.attributes.mall;会话边界键:mallId) */
|
|
16
|
+
export interface MallScope {
|
|
17
|
+
/**
|
|
18
|
+
* 小程序 appId(可选项——v4 缺陷修复):
|
|
19
|
+
* 客户端发送侧本就条件附加(miniAppId 不知则省略),服务端此前必填造成
|
|
20
|
+
* mallId-only 会话 N1 死锁(默认注入形态被形状守卫拒绝)。miniAppId 是
|
|
21
|
+
* 尽力回填的展示/定位值,非会话边界(边界恒 mallId)。
|
|
22
|
+
*/
|
|
23
|
+
miniAppId?: string;
|
|
24
|
+
/** 商圈 id(会话边界:跨 mall 强制新会话,011 F3) */
|
|
25
|
+
mallId: string;
|
|
26
|
+
/** 可选主体 id(会员体系 subject) */
|
|
27
|
+
subjectId?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 011 S7:服务端回显形状(= MallScope + 服务端生成的 sessionId;与输入形状分离) */
|
|
31
|
+
export interface MallSessionScope extends MallScope {
|
|
32
|
+
sessionId: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const mallScopeSchema = z.object({
|
|
36
|
+
miniAppId: z.string().min(1).optional(),
|
|
37
|
+
mallId: z.string().min(1),
|
|
38
|
+
subjectId: z.string().optional(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* mall 槽位(上下文统一化 Stage 2):TIER0 ContextSlot 协议上的 TIER1 定义——
|
|
43
|
+
* attributes.mall 的结构化读取(zod safeParse,fail-soft)。decide / handler / 工具
|
|
44
|
+
* 内统一经 mallSlot.read(attributes) 取 scope,替代散落的 as 断言。
|
|
45
|
+
*/
|
|
46
|
+
export const mallSlot = defineContextSlot({
|
|
47
|
+
key: MALL_SCOPE_KEY,
|
|
48
|
+
parse: (raw) => {
|
|
49
|
+
if (!raw || typeof raw !== 'object') return undefined;
|
|
50
|
+
const result = mallScopeSchema.safeParse(raw);
|
|
51
|
+
return result.success ? result.data : undefined;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/** 解析失败返回 undefined(不 throw),模块自行降级(011 B2 语义;mallSlot 薄包装) */
|
|
56
|
+
export function parseMallScope(attributes: Record<string, unknown> | undefined): MallScope | undefined {
|
|
57
|
+
return mallSlot.read(attributes);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 从 RequestContext 提取 MallScope(parseMallScope 包装) */
|
|
61
|
+
export function extractMallScope(ctx: RequestContext): MallScope | undefined {
|
|
62
|
+
return parseMallScope(ctx.attributes);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 模块 ports 解析槽位统一 scope 形状(017 槽位原子化:resolveService 入参单一来源)。
|
|
67
|
+
* 行为钉死:返回 `{ mallId } | {}`(flat,不含 miniAppId)——与四模块 ports 既有行为
|
|
68
|
+
* 逐字等价;补 miniAppId 会改变厂商加签身份解析器的凭据 scope 匹配路径。
|
|
69
|
+
*/
|
|
70
|
+
export function mallScopeOf(reqCtx: { attributes?: Record<string, unknown> }): Record<string, string> {
|
|
71
|
+
const mallId = parseMallScope(reqCtx.attributes ?? {})?.mallId;
|
|
72
|
+
return mallId ? { mallId } : {};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** mall 领域运行上下文(attributes 含 mall 的 RequestContext 收窄)
|
|
76
|
+
*
|
|
77
|
+
* @deprecated(上下文统一化 Stage 2):双断言供养的类型收窄已被 mallSlot.read /
|
|
78
|
+
* parseMallScope 显式解析取代——调用点直接传 RequestContext,消费侧 fail-soft 解析。
|
|
79
|
+
* 对外移除已随 0.8.0 落地(0.7.3 additive 波次保留至本波次)。
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 登录 context 宽松读取 mallId(F2:换码器归 provider 包后的共享读取器)。
|
|
84
|
+
* 登录 URL 可只带 mallId(不套 parseMallScope 全形状)——登录换码先例,与
|
|
85
|
+
* MALL_SCOPE_KEY 共置单一真源;provider 包 scoped 谓词与换码体共用本入口。
|
|
86
|
+
*/
|
|
87
|
+
export function readLoginMallId(context?: Readonly<Record<string, unknown>>): string | undefined {
|
|
88
|
+
const mall = context?.[MALL_SCOPE_KEY];
|
|
89
|
+
if (!mall || typeof mall !== 'object') return undefined;
|
|
90
|
+
const mallId = (mall as Record<string, unknown>).mallId;
|
|
91
|
+
return typeof mallId === 'string' && mallId.length > 0 ? mallId : undefined;
|
|
92
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TIER1 服务端子路径出口(F2 模板中立化后):
|
|
3
|
+
* 请求凭证读取助手(request-credentials)+ mall 身份视图(mall-context)+
|
|
4
|
+
* mall 控制平面投影(mall-control-plane)。
|
|
5
|
+
* 登录换码器已整迁各 provider 包自注册
|
|
6
|
+
* (ctx.authExchanges.register,@tbox.cn/app-provider-<vendor>/server);
|
|
7
|
+
* 本包不再承载任何厂商实现与装配知识。
|
|
8
|
+
*/
|
|
9
|
+
export * from './request-credentials';
|
|
10
|
+
export * from './mall-context';
|
|
11
|
+
export * from './mall-control-plane';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mall 身份视图(上下文统一化 Stage 2):RequestContext → 一屏业务身份字段。
|
|
3
|
+
*
|
|
4
|
+
* 收敛「怎么带 mallId / userId / token / memberId / openId」的散装读取——
|
|
5
|
+
* identity 原样保留(source 四态可判;登录态 = source !== 'anonymous'),
|
|
6
|
+
* scope 经 mallSlot fail-soft 解析,凭证字段经 request-credentials 单源访问器。
|
|
7
|
+
*/
|
|
8
|
+
import type { Identity, RequestContext } from '@tbox.cn/app-contracts';
|
|
9
|
+
import { extractMallScope } from '../scope';
|
|
10
|
+
import type { MallScope } from '../scope';
|
|
11
|
+
import { credentialsToken, credentialsMemberId, credentialsOpenId } from './request-credentials';
|
|
12
|
+
|
|
13
|
+
/** mall 域业务身份视图(模块 handler / 工具 / 卡片动作统一消费形态) */
|
|
14
|
+
export interface MallIdentityView {
|
|
15
|
+
/** 原样保留(source: local/external/dev/anonymous 四态可判) */
|
|
16
|
+
identity: Identity;
|
|
17
|
+
/** mall scope(attributes.mall fail-soft 解析;缺失/非法 → undefined,调用方降级) */
|
|
18
|
+
scope?: MallScope;
|
|
19
|
+
/** 登录 token(credentials.token;tokenless / 匿名 → ''——空串语义见 request-credentials) */
|
|
20
|
+
sessionToken: string;
|
|
21
|
+
/** 厂商 memberId(credentials.attributes.memberId) */
|
|
22
|
+
memberId?: string;
|
|
23
|
+
/** 平台 openId(credentials.attributes.openid——绑定场景;契约唯一通道) */
|
|
24
|
+
openId?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 由 RequestContext 组装 mall 身份视图(纯函数,无 IO) */
|
|
28
|
+
export function mallIdentityView(reqCtx: RequestContext): MallIdentityView {
|
|
29
|
+
const memberId = credentialsMemberId(reqCtx);
|
|
30
|
+
const openId = credentialsOpenId(reqCtx);
|
|
31
|
+
const scope = extractMallScope(reqCtx);
|
|
32
|
+
return {
|
|
33
|
+
identity: reqCtx.identity,
|
|
34
|
+
...(scope ? { scope } : {}),
|
|
35
|
+
sessionToken: credentialsToken(reqCtx),
|
|
36
|
+
...(memberId ? { memberId } : {}),
|
|
37
|
+
...(openId ? { openId } : {}),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mall 控制平面投影(integrations v4):root instances 注册表 → MallDeploymentControlPlane。
|
|
3
|
+
* 恒返回控制平面(无表 → 空平面)——模板装配零拓扑分支。
|
|
4
|
+
* attributes 赋义 zod 单源(原双模板 deployment.ts 内嵌 zod 上收——消灭 base/mall 双份漂移):
|
|
5
|
+
* strict 拒拼错键(strip 会让消费点恒 undefined——静默配置失效),错误消息列允许键清单。
|
|
6
|
+
* TIER0 attributes 全程 opaque,域赋义唯一发生地 = 本文件(011 B1 边界保持)。
|
|
7
|
+
*
|
|
8
|
+
* 隐式默认单实例融合(provider 声明):零拓扑 config(无 instances/defaultInstanceId)+
|
|
9
|
+
* root.provider 命中声明键 → 注入声明实例(default 经 normalizeInstances 单实例唯一解派生
|
|
10
|
+
* ——零新规则)。显式配置恒赢;规则细节见 mergeDeclaredInstances。
|
|
11
|
+
*/
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
import { normalizeInstances } from '@tbox.cn/app-contracts';
|
|
14
|
+
import type { DeploymentInstance, IntegrationsConfig, ProviderInstanceDeclarations } from '@tbox.cn/app-contracts';
|
|
15
|
+
import type { MallDeploymentControlPlane, MallDeploymentMall } from '../deployment';
|
|
16
|
+
|
|
17
|
+
/** mall 域 attributes 赋义(strict——拼错键 fail-fast,报错附允许键清单) */
|
|
18
|
+
const mallAttributesSchema = z.strictObject({
|
|
19
|
+
/** 小程序 appId(mall 级共享——默认商圈拼装 HELLO context.mall.miniAppId 的承载) */
|
|
20
|
+
miniAppId: z.string().min(1).optional(),
|
|
21
|
+
/** 客服电话(shopping-guide customer-service-faq 卡 link 拨打条目消费,缺省不渲染) */
|
|
22
|
+
contactPhone: z.string().min(1).optional(),
|
|
23
|
+
/** 地理坐标(WGS-84;有值时参与 /api/m/mall-home/nearby-mall 就近推荐,缺省不参与排序) */
|
|
24
|
+
location: z
|
|
25
|
+
.object({
|
|
26
|
+
lat: z.number(),
|
|
27
|
+
lon: z.number(),
|
|
28
|
+
/** 展示地址(可选;推荐 Banner 展示用) */
|
|
29
|
+
address: z.string().optional(),
|
|
30
|
+
})
|
|
31
|
+
.optional(),
|
|
32
|
+
// 门店维度(stores)显式非目标(D13)——新行业拓扑 = 新 TIER1 投影包,不在本文件扩键
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/** attributes 逐条赋义(safeParse 包装——ZodError 裸 JSON 难读,重抛附实例 id 与允许键清单) */
|
|
36
|
+
function parseMallAttributes(
|
|
37
|
+
inst: DeploymentInstance,
|
|
38
|
+
): MallDeploymentMall {
|
|
39
|
+
const result = mallAttributesSchema.safeParse(inst.attributes ?? {});
|
|
40
|
+
if (!result.success) {
|
|
41
|
+
const detail = result.error.issues
|
|
42
|
+
.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`)
|
|
43
|
+
.join('; ');
|
|
44
|
+
throw new Error(
|
|
45
|
+
`integrations instances[${inst.id}].attributes 赋义失败(允许键:miniAppId/contactPhone/location)——${detail}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return { mallId: inst.id, name: inst.name, ...result.data };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 隐式默认单实例融合(五规则;no-op 分支零分配——原 config 引用返回):
|
|
53
|
+
* 1. declared 缺席 → 原样;
|
|
54
|
+
* 2. config.instances 非空 → 原样(显式表赢);
|
|
55
|
+
* 3. config.defaultInstanceId 显式在场(含 null——「显式无默认」即真值)→ 原样;
|
|
56
|
+
* 4. 零拓扑 + root.provider(root 层恒 string——inline 仅 service 层)命中声明键且声明
|
|
57
|
+
* 非空 → 注入声明实例(default 走 normalizeInstances 既有唯一解);
|
|
58
|
+
* 5. 其余(root.provider 缺席 / 声明无该键 / 空声明数组)→ 原样。
|
|
59
|
+
* 空声明数组短路守 wire 空表不变量(zod 拒空 instances 数组——「无表 = 键缺席唯一写法」;
|
|
60
|
+
* 融合发生在加载校验后,注入空表会绕过 zod 制造非法形态)。
|
|
61
|
+
*/
|
|
62
|
+
function mergeDeclaredInstances(
|
|
63
|
+
config: IntegrationsConfig,
|
|
64
|
+
declared: ProviderInstanceDeclarations | undefined,
|
|
65
|
+
): IntegrationsConfig {
|
|
66
|
+
if (!declared) return config;
|
|
67
|
+
if (config.instances && config.instances.length > 0) return config;
|
|
68
|
+
if (config.defaultInstanceId !== undefined) return config;
|
|
69
|
+
const rootProvider = config.provider;
|
|
70
|
+
if (!rootProvider) return config;
|
|
71
|
+
const instances = declared[rootProvider];
|
|
72
|
+
if (!instances || instances.length === 0) return config;
|
|
73
|
+
return { ...config, instances: [...instances] };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* integrations config → mall 控制平面(装配链唯一入口):
|
|
78
|
+
* - declared = provider 声明快照(缺省关闭——既有调用方零变化;消费方 = mall-core project());
|
|
79
|
+
* - instances 注册表逐条投影(id→mallId、name、attributes 赋义展开——声明实例过同一
|
|
80
|
+
* zod strict 校验,坏键 fail-fast 与显式配置同律);
|
|
81
|
+
* - defaultMallId = normalizeInstances 解析(显式 ?? 单实例唯一解;无 → undefined)。
|
|
82
|
+
*/
|
|
83
|
+
export function mallControlPlaneOf(
|
|
84
|
+
config: IntegrationsConfig,
|
|
85
|
+
declared?: ProviderInstanceDeclarations,
|
|
86
|
+
): MallDeploymentControlPlane {
|
|
87
|
+
const { instances, defaultInstanceId } = normalizeInstances(
|
|
88
|
+
mergeDeclaredInstances(config, declared),
|
|
89
|
+
);
|
|
90
|
+
const malls: MallDeploymentMall[] = instances.map(parseMallAttributes);
|
|
91
|
+
return { malls, ...(defaultInstanceId !== null ? { defaultMallId: defaultInstanceId } : {}) };
|
|
92
|
+
}
|