@tbox.cn/app-provider-mock 0.2.0 → 0.3.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 CHANGED
@@ -7,3 +7,41 @@ promotion×4 / shopping-guide×4 + mall.info,17 槽全 local:true——免传
7
7
  中立化("示例会员卡");shopping 域为静态 seed 派生(门店/类目/楼层目录)。
8
8
  - 模板缺省绑定(F2):裸跑即完整 mock 演示——免业务凭据,不免模型密钥。
9
9
  - 装配走 `tbox-app module add`(provider 即无卡模块)。
10
+
11
+ ## 实例配置(config schemas,0.3.0)
12
+
13
+ 17 槽全带 `configSchema`(`schemas/` 五文件按域共享:member / parking / promotion / shopping /
14
+ mall-info)——面板动态表单 / CLI / 手编三动线同一真源。config 经平台五层浅合并
15
+ (root.config → root.configByInstance → `domains.<域>.config` → 槽级 config → 实例级 config,
16
+ 键级覆盖)以 `input.config` 传入工厂,各实现构造期一次 fail-soft 解析(`src/server/config.ts`):
17
+
18
+ - **零配置 = 零行为变化**:33 字段缺省 ≡ 历史硬编码值(回归判据,既有测试零改动锁死)。
19
+ - **非法值静默回退缺省**:类型不符 / NaN / 整数字段分式 / 空串——schema 是表单引导非门禁,
20
+ 守卫在消费侧(`*_CFG_INT_KEYS` 整数约束 + shopping 楼层白名单回退)。
21
+ - **空串语义**:`mall.info.name` 空串 = 动态别名 `示例商场(mallId)` 回退;`shopping.extraShopName`
22
+ 空串 = 不追加门店;`member.levelName` 空串 = 卡面无等级区。
23
+
24
+ 字段速览(域 → 应用可见面):
25
+
26
+ | 域 | 字段 | 可见面 |
27
+ |---|---|---|
28
+ | member | cardName / initialPoints / levelName / memberName | 卡面名 / 积分大数字(含流水金额派生)/ 等级区 / 资料标签 |
29
+ | member | enrollUrl / loginUrl | 入会 / 登录 CTA 深链 |
30
+ | parking | parkName / feeName / feeCents | 信息卡标题 / 费用项 / 缴费卡金额(报价·订单·记录同源) |
31
+ | parking | memberBenefitEnabled / memberBenefitDiscountCents / pointsBenefitEnabled | 权益勾选区与 reprice 折扣联动 |
32
+ | parking | invoiceEnabled / remainingSpaces / firstHourRate / vehicleFloor / vehicleZone / vehicleSpaceNo | 开票按钮显隐 / 车位 / 寻车卡 |
33
+ | promotion | couponTitle / couponValueCents / couponThresholdCents / couponValidityDays | 券列表 / 详情 / 资产(标题派生)/ 效期 |
34
+ | promotion | grouponEnabled / activityTitle | 优惠列表项数 / 活动卡标题 |
35
+ | shopping | extraShopName / extraShopCategory / extraShopFloor / extraShopUnit / extraShopOpen | 门店列表 / 类目聚合 / 楼层目录新增一行 |
36
+ | mall-info | name / address / latitude / longitude | 聊天品牌区 mallName / mall-info agent 答问数据面 |
37
+
38
+ **扩展约定(3 触点 + 1)**:schema 行(`schemas/<域>.config.json`)+ DEFAULTS 行(impl 同文件
39
+ `*_CFG_DEFAULTS`)+ 消费点改引 `this.cfg.*`;整数字段追加 `*_CFG_INT_KEYS` 一项。双锁测试
40
+ (`tests/config-schema.test.ts`)自动覆盖:键集双向 / 缺省漂移 / 整数对偶 / manifest 分发面 /
41
+ 楼层 enum 同值。
42
+
43
+ **运营语义**:手编 `integrations.json` 后 dev 不自动重启(tsx watch 对 config 盲)——手动重启;
44
+ 面板保存走 RunManager `onApplied` 自动重启。**域级引导**:mock 同域多槽各自实例化(级联按槽求值)
45
+ ——凡跨槽可见的键建议配 `domains.<域>`:member 全部键 / parking.parkName·feeCents /
46
+ promotion.couponTitle(券列表 + 我的券双槽派生)/ shopping-guide.extraShop*(四槽目录联动);
47
+ 槽级仅用于单槽差异键(如 `promotion.offers.couponValueCents`、`mall.info.name`)。
@@ -2,16 +2,11 @@ import { ServiceAdapterFactory, ServerContext } from '@tbox.cn/app-sdk/server';
2
2
  import { MemberAccountPort, MemberProfilePort, MemberCardPort, MemberBenefitsPort, MemberEnrollmentPort, MemberAccountSnapshot, MemberProfileSnapshot, MemberCardSnapshot, Points, MemberBenefitsSnapshot, PointUsageRecord, DiscountRule, MemberEnrollmentInput, MemberEnrollmentResult, MemberEnrollmentFormDefinition, ParkingQueryPort, ParkingPaymentPort, ParkingVehiclePort, ParkingQueryContext, QuoteSnapshot, ParkingVehicle, ParkingRecordPage, ParkingPaymentRecord, ParkingInfo, ParkingBenefit, ParkingLocation, PromotionOfferQueryPort, PromotionEntitlementPort, PromotionActivityPort, PromotionAcquisitionPort, PromotionQueryContext, PromotionSearchInput, PromotionOfferPage, PromotionOffer, PromotionEntitlementPage, PromotionEntitlement, PromotionActivityPage, PromotionActivityDetail, PromotionOperation, CatalogQueryPort, ShopDirectoryQueryPort, CommercialCategoryQueryPort, MallDirectoryQueryPort, ShopSearchCriteria, ShoppingPage, ShopRecord, ShoppingQueryContext, CategoryRecord, MallDirectoryRecord, ProductSearchCriteria, ProductRecord, ProductFacetsRecord } from '@tbox.cn/app-contracts-mall';
3
3
  import { Router } from 'express';
4
4
 
5
- /**
6
- * Mock member 实现(provider-mock 自持——R3-1 裁决:provider 不得依赖业务模块,实现自持
7
- * 而非包装 module-member InMemory)。演示面最小集:开户/卡面/积分/流水/入会闭环
8
- * (demo ref 恒 `demo:` 前缀,厂商中立)。
9
- */
10
-
11
5
  type MockMemberService = MemberAccountPort & MemberProfilePort & MemberCardPort & MemberBenefitsPort & MemberEnrollmentPort;
12
6
  /** mock 壳侧登录用户(换码器固定身份;mock 语义 = 单 demo 用户/部署) */
13
7
  declare const MOCK_SHELL_USER_ID = "demo-user";
14
- /** 入会落地(幂等):enroll 路由/enroll() 共用;余额缺省 300;登录成功即清除登出态 */
8
+ /** 入会落地(幂等):enroll 路由/enroll() 共用;余额不写种子——查询侧按实例配置惰性读
9
+ *(默认 300,见 currentBalanceOf);登录成功即清除登出态 */
15
10
  declare function enrollMockMember(userId: string): void;
16
11
  declare function isMockMemberEnrolled(userId: string): boolean;
17
12
  /** 登出标记(mock 档登出态模拟:仅对已入会用户有意义;重登录即清除) */
@@ -25,16 +20,20 @@ declare class MockMemberServiceImpl implements MockMemberService {
25
20
  * 透传素材,mock 出厂面恒配 demo 值)。demo 值厂商中立(appId=demo),点击在壳内
26
21
  * navigateTo 失败即无害降级。
27
22
  */
28
- readonly enrollUrl = "alipays://platformapi/startapp?appId=demo&page=v/index/index";
23
+ readonly enrollUrl: string;
29
24
  /** 登录页深链(同上,可选透传素材;供未登录引导卡跳转)。 */
30
- readonly loginUrl = "alipays://platformapi/startapp?appId=demo&page=v/login/index";
25
+ readonly loginUrl: string;
26
+ /** 配置快照(fail-soft,构造期一次解析;键集 ≡ MEMBER_CFG_DEFAULTS) */
27
+ private readonly cfg;
28
+ /** 积分流水(金额随 initialPoints 派生——与余额同源) */
29
+ private readonly usage;
31
30
  /** 默认访客(无种子):demo-user 初始 not_enrolled,经入会闭环翻为 active */
32
- constructor(initial?: Record<string, number>);
31
+ constructor(initial?: Record<string, number>, config?: unknown);
33
32
  getAccount(userId: string): Promise<MemberAccountSnapshot>;
34
33
  getProfile(_userId: string): Promise<MemberProfileSnapshot>;
35
34
  getCard(userId: string): Promise<MemberCardSnapshot | null>;
36
35
  queryPoints(userId: string): Promise<Points>;
37
- getBenefits(_userId: string): Promise<MemberBenefitsSnapshot>;
36
+ getBenefits(userId: string): Promise<MemberBenefitsSnapshot>;
38
37
  listPointUsage(_userId: string, page: number, pageSize: number): Promise<{
39
38
  items: PointUsageRecord[];
40
39
  total: number;
@@ -43,6 +42,8 @@ declare class MockMemberServiceImpl implements MockMemberService {
43
42
  deductPoints(userId: string, amount: number, _key: string): Promise<boolean>;
44
43
  refundPoints(userId: string, amount: number, _key: string): Promise<boolean>;
45
44
  awardPoints(userId: string, amount: number): Promise<boolean>;
45
+ /** 余额单源:显式增减覆盖值 ??(已入会 ? 配置初值 : 0)——入会不写种子(惰性读) */
46
+ private currentBalanceOf;
46
47
  enroll(input: MemberEnrollmentInput): Promise<MemberEnrollmentResult>;
47
48
  getEnrollmentStatus(userId: string, _attemptRef: string): Promise<{
48
49
  status: "active";
@@ -57,18 +58,21 @@ declare class MockMemberServiceImpl implements MockMemberService {
57
58
  getEnrollmentFormDefinition(): Promise<MemberEnrollmentFormDefinition>;
58
59
  }
59
60
 
60
- /**
61
- * Mock parking 实现(provider-mock 自持——R3-1 裁决)。演示面最小集:报价/车辆/记录/
62
- * 综合信息/权益/寻车 + 三步支付链 mock 闭环(reconcile 恒 PAID 收口)。demo ref 恒
63
- * `demo:` 前缀,厂商中立;车牌未提供时以演示车(demo-plate)兜底。
64
- */
65
-
66
61
  type MockParkingService = ParkingQueryPort & ParkingPaymentPort & ParkingVehiclePort;
67
62
  declare class MockParkingServiceImpl implements MockParkingService {
68
63
  private vehicles;
69
64
  private records;
70
- constructor(mallId?: string);
65
+ /** 配置快照(fail-soft,构造期一次解析;键集 ≡ PARKING_CFG_DEFAULTS) */
66
+ private readonly cfg;
67
+ constructor(mallId?: string, config?: unknown);
71
68
  private mallId;
69
+ /**
70
+ * kind 必须下发:客户端按它分流(discount = 自动生效、不进勾选区;points = 积分入口),
71
+ * 服务端 projectPaymentOptions 也只认 kind==='points'。缺省时整套分流静默失效——
72
+ * 等级权益会退化成一个可勾选项,积分入口则从不出现。
73
+ */
74
+ private benefits;
75
+ private quote;
72
76
  queryFee(q: ParkingQueryContext, input: {
73
77
  plateNo?: string;
74
78
  vehicleRef?: string;
@@ -108,13 +112,15 @@ declare class MockParkingServiceImpl implements MockParkingService {
108
112
  }): Promise<void>;
109
113
  }
110
114
 
111
- /**
112
- * Mock promotion 实现(provider-mock 自持——R3-1 裁决)。演示面最小集:搜索/精查/推荐/
113
- * 资产列表/活动列表/领取闭环(acquire 恒 SUCCEEDED)。demo ref 恒 `demo:` 前缀,厂商中立。
114
- */
115
-
116
115
  type MockPromotionService = PromotionOfferQueryPort & PromotionEntitlementPort & PromotionActivityPort & PromotionAcquisitionPort;
117
116
  declare class MockPromotionServiceImpl implements MockPromotionService {
117
+ /** 配置快照(fail-soft,构造期一次解析;键集 ≡ PROMOTION_CFG_DEFAULTS) */
118
+ private readonly cfg;
119
+ /** 供/资产/活动三表(构造期按 cfg 构建——请求级实例,成本可忽略) */
120
+ private readonly offers;
121
+ private readonly entitlements;
122
+ private readonly activities;
123
+ constructor(config?: unknown);
118
124
  searchOffers(_q: PromotionQueryContext, input: PromotionSearchInput): Promise<PromotionOfferPage>;
119
125
  getOfferDetail(_q: PromotionQueryContext, input: {
120
126
  offerRef?: string;
@@ -138,20 +144,6 @@ declare class MockPromotionServiceImpl implements MockPromotionService {
138
144
  getOperation(_q: PromotionQueryContext, operationRef: string): Promise<PromotionOperation | null>;
139
145
  }
140
146
 
141
- /**
142
- * Mock shopping 实现(provider-mock 自持——R3-1 裁决:provider 不得依赖业务模块)。
143
- * 演示面:门店/类目/楼层目录(remote 检索域四键形状的 catalog/shop/category/directory)。
144
- *
145
- * 设计决策:
146
- * - 单一真源:DEMO_BUILDINGS 楼栋→楼层→门店 seed;类目/楼层业态/计数派生聚合;
147
- * floorCode/buildingCode 派生同值(code=name,消除 mall-context id ↔ search-shops
148
- * 过滤键隐式映射)。
149
- * - 纯 mock:静态数组 + 内存过滤,零 IO、零外部依赖(不依赖知识库/TBOX_API_KEY/datasetId)。
150
- * - merchantId 连字符格式(demo-shop-NNNN):LLM 从卡片 value 提取友好;getShop 入参 trim。
151
- * - catalog 空实现(对齐真实档 C 端):演示面不覆盖商品目录,门店链路为演示重点。
152
- * - 构造器接受 seed(默认 DEMO_BUILDINGS):测试可注入定制数据。
153
- */
154
-
155
147
  interface ShopSeed {
156
148
  merchantId: string;
157
149
  name: string;
@@ -172,10 +164,14 @@ interface DemoBuilding {
172
164
  floors: DemoFloor[];
173
165
  }
174
166
  declare class MockShoppingServiceImpl implements CatalogQueryPort, ShopDirectoryQueryPort, CommercialCategoryQueryPort, MallDirectoryQueryPort {
167
+ /** 配置快照(fail-soft,构造期一次解析;键集 ≡ SHOPPING_CFG_DEFAULTS) */
168
+ private readonly cfg;
175
169
  private readonly buildings;
176
170
  private readonly shops;
177
171
  private readonly categories;
178
- constructor(seed?: readonly DemoBuilding[]);
172
+ constructor(seed?: readonly DemoBuilding[], config?: unknown);
173
+ /** extraShopName 非空 → 指定楼层追加演示门店(名称 trim 判空 + 楼层白名单回退——消费点守卫) */
174
+ private withExtraShop;
179
175
  searchShops(criteria: ShopSearchCriteria): Promise<ShoppingPage<ShopRecord>>;
180
176
  getShop(shopId: string, _ctx: ShoppingQueryContext): Promise<ShopRecord | null>;
181
177
  listCategories(_ctx: ShoppingQueryContext): Promise<readonly CategoryRecord[]>;
@@ -220,6 +216,13 @@ declare function createMockEnrollRouter(): Router;
220
216
  * (catalog 空 + shop/category/directory 纯 mock 数据);assistance/navigation 本地知识库域
221
217
  * 归模块本地 adapter,不属 provider 供给面。
222
218
  *
219
+ * config 消费:五域工厂透传 input.config → 各 impl 构造期 fail-soft 快照
220
+ * (src/server/config.ts;键集/缺省双锁 tests/config-schema.test.ts);零配置 = 零行为变化。
221
+ *
222
+ * 隐式默认单实例(零拓扑部署兜底):register 期经 ctx.integrations.declareProviderInstances
223
+ * 声明 mall-demo——显式 instances/defaultInstanceId 恒赢(融合规则见 contracts-mall
224
+ * mallControlPlaneOf);值与 .real-env mock 档演示语义一致(星河 Mock 小镇 / mock-demo-app)。
225
+ *
223
226
  * 入会体验闭环(C5)追加两件:
224
227
  * - scoped mock 换码器(ctx.authExchanges):固定 demo-user、GUEST/MEMBER 双形态对齐
225
228
  * 真实厂商换码协议(潜客 token=''+openid ↔ 会员 token+memberId)——mock 档冷登走与