@ahoo-wang/fetcher-wow 3.17.1 → 3.18.1
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 +35 -908
- package/README.zh-CN.md +35 -661
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +618 -397
- package/dist/index.es.js.map +1 -1
- package/dist/query/aggregation.d.ts +131 -0
- package/dist/query/aggregation.d.ts.map +1 -0
- package/dist/query/event/eventStreamQueryApi.d.ts +1 -0
- package/dist/query/event/eventStreamQueryApi.d.ts.map +1 -1
- package/dist/query/event/eventStreamQueryClient.d.ts +6 -0
- package/dist/query/event/eventStreamQueryClient.d.ts.map +1 -1
- package/dist/query/filter.d.ts +42 -22
- package/dist/query/filter.d.ts.map +1 -1
- package/dist/query/index.d.ts +1 -0
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/queryApi.d.ts +6 -0
- package/dist/query/queryApi.d.ts.map +1 -1
- package/dist/query/snapshot/snapshotQueryApi.d.ts +1 -0
- package/dist/query/snapshot/snapshotQueryApi.d.ts.map +1 -1
- package/dist/query/snapshot/snapshotQueryClient.d.ts +6 -0
- package/dist/query/snapshot/snapshotQueryClient.d.ts.map +1 -1
- package/package.json +3 -3
package/README.zh-CN.md
CHANGED
|
@@ -1,682 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@ahoo-wang/fetcher-wow`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
[](https://codecov.io/gh/Ahoo-Wang/fetcher)
|
|
6
|
-
[](https://github.com/Ahoo-Wang/fetcher/blob/main/LICENSE)
|
|
7
|
-
[](https://www.npmjs.com/package/@ahoo-wang/fetcher-wow)
|
|
8
|
-
[](https://www.npmjs.com/package/@ahoo-wang/fetcher-wow)
|
|
9
|
-
[](https://deepwiki.com/Ahoo-Wang/fetcher)
|
|
10
|
-
[](https://fetcher.ahoo.me/?path=/docs/wow-introduction--docs)
|
|
3
|
+
面向 Wow 命令、快照、领域事件、过滤、分页与聚合的类型化 Fetcher 客户端和契约。只在
|
|
4
|
+
对接 Wow HTTP 端点时使用。
|
|
11
5
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
## 🌟 特性
|
|
15
|
-
|
|
16
|
-
- **📦 完整的 TypeScript 支持**:为所有 Wow 框架实体提供完整的类型定义,包括命令、事件和查询
|
|
17
|
-
- **🚀 命令客户端**:用于向 Wow 服务发送命令的高级客户端,支持同步和流式响应
|
|
18
|
-
- **🔍 强大的查询 DSL**:类型安全的 `FilterExpression` 构建器,支持完整的查询操作符
|
|
19
|
-
- **📡 实时事件流**:内置对服务器发送事件的支持,用于接收实时命令结果和数据更新
|
|
20
|
-
- **🔄 CQRS 模式实现**:对命令查询责任分离架构模式的一流支持
|
|
21
|
-
- **🧱 DDD 基础构件**:基本的领域驱动设计构建块,包括聚合、事件和值对象
|
|
22
|
-
- **🔍 查询客户端**:专门用于查询快照和事件流数据的客户端,支持全面的查询操作:
|
|
23
|
-
- 资源计数
|
|
24
|
-
- 资源列表查询
|
|
25
|
-
- 以服务器发送事件形式流式传输资源
|
|
26
|
-
- 资源分页
|
|
27
|
-
- 单个资源检索
|
|
28
|
-
|
|
29
|
-
## 🚀 快速开始
|
|
30
|
-
|
|
31
|
-
### 安装
|
|
6
|
+
## 安装
|
|
32
7
|
|
|
33
8
|
```bash
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# 使用 pnpm
|
|
38
|
-
pnpm add @ahoo-wang/fetcher-wow
|
|
39
|
-
|
|
40
|
-
# 使用 yarn
|
|
41
|
-
yarn add @ahoo-wang/fetcher-wow
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## 📚 API 参考
|
|
45
|
-
|
|
46
|
-
### 命令模块
|
|
47
|
-
|
|
48
|
-
#### CommandResult
|
|
49
|
-
|
|
50
|
-
表示命令执行结果的接口:
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
import { CommandResult, CommandStage } from '@ahoo-wang/fetcher-wow';
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
#### CommandClient
|
|
57
|
-
|
|
58
|
-
用于向 Wow 框架发送命令的 HTTP 客户端。该客户端提供了同步或流式接收命令结果的方法。
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
import {
|
|
62
|
-
Fetcher,
|
|
63
|
-
FetchExchange,
|
|
64
|
-
HttpMethod,
|
|
65
|
-
RequestInterceptor,
|
|
66
|
-
URL_RESOLVE_INTERCEPTOR_ORDER,
|
|
67
|
-
} from '@ahoo-wang/fetcher';
|
|
68
|
-
import '@ahoo-wang/fetcher-eventstream';
|
|
69
|
-
import {
|
|
70
|
-
CommandClient,
|
|
71
|
-
CommandRequest,
|
|
72
|
-
CommandHeaders,
|
|
73
|
-
CommandStage,
|
|
74
|
-
} from '@ahoo-wang/fetcher-wow';
|
|
75
|
-
import { idGenerator } from '@ahoo-wang/fetcher-cosec';
|
|
76
|
-
|
|
77
|
-
// 使用基础配置创建 fetcher 实例
|
|
78
|
-
const exampleFetcher = new Fetcher({
|
|
79
|
-
baseURL: 'http://localhost:8080/',
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// 定义当前用户 ID
|
|
83
|
-
const currentUserId = idGenerator.generateId();
|
|
84
|
-
|
|
85
|
-
// 创建处理 URL 参数的拦截器
|
|
86
|
-
class AppendOwnerId implements RequestInterceptor {
|
|
87
|
-
readonly name: string = 'AppendOwnerId';
|
|
88
|
-
readonly order: number = URL_RESOLVE_INTERCEPTOR_ORDER - 1;
|
|
89
|
-
|
|
90
|
-
intercept(exchange: FetchExchange) {
|
|
91
|
-
const urlParams = exchange.ensureRequestUrlParams();
|
|
92
|
-
urlParams.path['ownerId'] = currentUserId;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// 注册拦截器
|
|
97
|
-
exampleFetcher.interceptors.request.use(new AppendOwnerId());
|
|
98
|
-
|
|
99
|
-
// 创建命令客户端
|
|
100
|
-
const cartCommandClient = new CommandClient({
|
|
101
|
-
fetcher: exampleFetcher,
|
|
102
|
-
basePath: 'owner/{ownerId}/cart',
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// 定义命令端点
|
|
106
|
-
class CartCommandEndpoints {
|
|
107
|
-
static readonly addCartItem = 'add_cart_item';
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// 定义命令接口
|
|
111
|
-
interface AddCartItem {
|
|
112
|
-
productId: string;
|
|
113
|
-
quantity: number;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
type AddCartItemCommand = CommandRequest<AddCartItem>;
|
|
117
|
-
|
|
118
|
-
// 创建命令请求
|
|
119
|
-
const addCartItemCommand: AddCartItemCommand = {
|
|
120
|
-
method: HttpMethod.POST,
|
|
121
|
-
headers: {
|
|
122
|
-
[CommandHeaders.WAIT_STAGE]: CommandStage.SNAPSHOT,
|
|
123
|
-
},
|
|
124
|
-
body: {
|
|
125
|
-
productId: 'productId',
|
|
126
|
-
quantity: 1,
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// 发送命令并等待结果
|
|
131
|
-
const commandResult = await cartCommandClient.send(
|
|
132
|
-
CartCommandEndpoints.addCartItem,
|
|
133
|
-
addCartItemCommand,
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
// 发送命令并接收流式结果
|
|
137
|
-
const commandResultStream = await cartCommandClient.sendAndWaitStream(
|
|
138
|
-
CartCommandEndpoints.addCartItem,
|
|
139
|
-
addCartItemCommand,
|
|
140
|
-
);
|
|
141
|
-
for await (const commandResultEvent of commandResultStream) {
|
|
142
|
-
console.log('收到命令结果:', commandResultEvent.data);
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
##### 方法
|
|
147
|
-
|
|
148
|
-
- `send(path: string, commandRequest: CommandRequest): Promise<CommandResult>` - 发送命令并等待结果。
|
|
149
|
-
- `sendAndWaitStream(path: string, commandRequest: CommandRequest): Promise<CommandResultEventStream>` -
|
|
150
|
-
发送命令并以服务器发送事件的形式返回结果流。
|
|
151
|
-
|
|
152
|
-
### 查询模块
|
|
153
|
-
|
|
154
|
-
#### FilterExpression 构建器
|
|
155
|
-
|
|
156
|
-
Wow 8.11+ 查询使用 `FilterExpression`:
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
import { DeletionState, filter } from '@ahoo-wang/fetcher-wow';
|
|
160
|
-
|
|
161
|
-
const expression = filter.and(
|
|
162
|
-
filter.deletion(DeletionState.ACTIVE),
|
|
163
|
-
filter.eq('state.status', 'PAID'),
|
|
164
|
-
filter.elementMatch('state.items', filter.gt('quantity', 0)),
|
|
165
|
-
filter.search('wow', 'state.name'),
|
|
166
|
-
);
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
所有构建器集中在 `filter`:`matchAll`、`matchNone`、`and`、`or`、`nor`、
|
|
170
|
-
比较、字符串/集合谓词、存在性检查、`elementMatch`、`search`、删除范围和相对时间过滤器。
|
|
171
|
-
|
|
172
|
-
#### 条件构建器(已弃用)
|
|
173
|
-
|
|
174
|
-
旧 Condition API 仅为兼容旧版 Wow 服务保留。新代码应使用
|
|
175
|
-
`FilterExpression` 和 `filter.*`。
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
import {
|
|
179
|
-
and,
|
|
180
|
-
or,
|
|
181
|
-
eq,
|
|
182
|
-
ne,
|
|
183
|
-
gt,
|
|
184
|
-
lt,
|
|
185
|
-
gte,
|
|
186
|
-
lte,
|
|
187
|
-
contains,
|
|
188
|
-
isIn,
|
|
189
|
-
notIn,
|
|
190
|
-
between,
|
|
191
|
-
allIn,
|
|
192
|
-
startsWith,
|
|
193
|
-
endsWith,
|
|
194
|
-
match,
|
|
195
|
-
elemMatch,
|
|
196
|
-
isNull,
|
|
197
|
-
notNull,
|
|
198
|
-
isTrue,
|
|
199
|
-
isFalse,
|
|
200
|
-
exists,
|
|
201
|
-
raw,
|
|
202
|
-
today,
|
|
203
|
-
beforeToday,
|
|
204
|
-
tomorrow,
|
|
205
|
-
thisWeek,
|
|
206
|
-
nextWeek,
|
|
207
|
-
lastWeek,
|
|
208
|
-
thisMonth,
|
|
209
|
-
lastMonth,
|
|
210
|
-
recentDays,
|
|
211
|
-
earlierDays,
|
|
212
|
-
active,
|
|
213
|
-
all,
|
|
214
|
-
id,
|
|
215
|
-
ids,
|
|
216
|
-
aggregateId,
|
|
217
|
-
aggregateIds,
|
|
218
|
-
tenantId,
|
|
219
|
-
ownerId,
|
|
220
|
-
} from '@ahoo-wang/fetcher-wow';
|
|
221
|
-
|
|
222
|
-
// 简单条件
|
|
223
|
-
const simpleConditions = [
|
|
224
|
-
eq('name', 'John'),
|
|
225
|
-
ne('status', 'inactive'),
|
|
226
|
-
gt('age', 18),
|
|
227
|
-
lt('score', 100),
|
|
228
|
-
gte('rating', 4.0),
|
|
229
|
-
lte('price', 100),
|
|
230
|
-
];
|
|
231
|
-
|
|
232
|
-
// 字符串条件
|
|
233
|
-
const stringConditions = [
|
|
234
|
-
contains('email', '@company.com'),
|
|
235
|
-
startsWith('username', 'j'),
|
|
236
|
-
endsWith('domain', '.com'),
|
|
237
|
-
isIn('status', 'active', 'pending'),
|
|
238
|
-
notIn('role', 'guest', 'banned'),
|
|
239
|
-
match('description', 'search keywords'),
|
|
240
|
-
];
|
|
241
|
-
|
|
242
|
-
// 空值检查
|
|
243
|
-
const nullConditions = [
|
|
244
|
-
isNull('deletedAt'),
|
|
245
|
-
notNull('email'),
|
|
246
|
-
isTrue('isActive'),
|
|
247
|
-
isFalse('isDeleted'),
|
|
248
|
-
exists('phoneNumber'),
|
|
249
|
-
];
|
|
250
|
-
|
|
251
|
-
// 数组条件
|
|
252
|
-
const arrayConditions = [
|
|
253
|
-
allIn('tags', 'react', 'typescript'),
|
|
254
|
-
elemMatch('items', eq('quantity', 0)),
|
|
255
|
-
];
|
|
256
|
-
|
|
257
|
-
// 日期条件
|
|
258
|
-
const dateConditions = [
|
|
259
|
-
today('createdAt'),
|
|
260
|
-
beforeToday('lastLogin', '09:30'),
|
|
261
|
-
tomorrow('scheduledDate'),
|
|
262
|
-
thisWeek('updatedAt'),
|
|
263
|
-
nextWeek('startDate'),
|
|
264
|
-
lastWeek('endDate'),
|
|
265
|
-
thisMonth('createdDate'),
|
|
266
|
-
lastMonth('expirationDate'),
|
|
267
|
-
recentDays('createdAt', 5), // 最近5天,包括今天
|
|
268
|
-
earlierDays('createdAt', 3), // 3天之前
|
|
269
|
-
];
|
|
270
|
-
|
|
271
|
-
// 复杂条件
|
|
272
|
-
const complexCondition = and(
|
|
273
|
-
eq('tenantId', 'tenant-123'),
|
|
274
|
-
or(
|
|
275
|
-
contains('email', '@company.com'),
|
|
276
|
-
isIn('department', 'engineering', 'marketing'),
|
|
277
|
-
),
|
|
278
|
-
between('salary', 50000, 100000),
|
|
279
|
-
today('createdAt'),
|
|
280
|
-
active(),
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
// 高级用法的原始条件
|
|
284
|
-
const rawCondition = raw({ $text: { $search: 'keywords' } });
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
**操作符参考:**
|
|
288
|
-
|
|
289
|
-
| 类别 | 操作符 |
|
|
290
|
-
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
291
|
-
| 逻辑 | `and`, `or`, `nor` |
|
|
292
|
-
| 比较 | `eq`, `ne`, `gt`, `lt`, `gte`, `lte` |
|
|
293
|
-
| 字符串 | `contains`, `startsWith`, `endsWith`, `match` |
|
|
294
|
-
| 集合 | `isIn`, `notIn`, `allIn`, `elemMatch` |
|
|
295
|
-
| 空值/布尔 | `isNull`, `notNull`, `isTrue`, `isFalse`, `exists` |
|
|
296
|
-
| 日期 | `today`, `beforeToday(time)`, `tomorrow`, `thisWeek`, `nextWeek`, `lastWeek`, `thisMonth`, `lastMonth`, `recentDays(days)`, `earlierDays(days)` |
|
|
297
|
-
| ID | `id`, `ids`, `aggregateId`, `aggregateIds`, `tenantId`, `ownerId` |
|
|
298
|
-
| 状态 | `active`, `all`, `deleted` |
|
|
299
|
-
| 特殊 | `raw`(用于高级数据库特定查询) |
|
|
300
|
-
|
|
301
|
-
#### SnapshotQueryClient
|
|
302
|
-
|
|
303
|
-
用于查询物化快照的客户端,支持全面的查询操作:
|
|
304
|
-
|
|
305
|
-
```typescript
|
|
306
|
-
import {
|
|
307
|
-
Fetcher,
|
|
308
|
-
FetchExchange,
|
|
309
|
-
RequestInterceptor,
|
|
310
|
-
URL_RESOLVE_INTERCEPTOR_ORDER,
|
|
311
|
-
} from '@ahoo-wang/fetcher';
|
|
312
|
-
import '@ahoo-wang/fetcher-eventstream';
|
|
313
|
-
import {
|
|
314
|
-
SnapshotQueryClient,
|
|
315
|
-
filter,
|
|
316
|
-
FilterListQuery,
|
|
317
|
-
FilterPagedQuery,
|
|
318
|
-
FilterSingleQuery,
|
|
319
|
-
} from '@ahoo-wang/fetcher-wow';
|
|
320
|
-
import { idGenerator } from '@ahoo-wang/fetcher-cosec';
|
|
321
|
-
|
|
322
|
-
interface CartItem {
|
|
323
|
-
productId: string;
|
|
324
|
-
quantity: number;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
interface CartState extends Identifier {
|
|
328
|
-
items: CartItem[];
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// 使用基础配置创建 fetcher 实例
|
|
332
|
-
const exampleFetcher = new Fetcher({
|
|
333
|
-
baseURL: 'http://localhost:8080/',
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
// 定义当前用户 ID
|
|
337
|
-
const currentUserId = idGenerator.generateId();
|
|
338
|
-
|
|
339
|
-
// 创建处理 URL 参数的拦截器
|
|
340
|
-
class AppendOwnerId implements RequestInterceptor {
|
|
341
|
-
readonly name: string = 'AppendOwnerId';
|
|
342
|
-
readonly order: number = URL_RESOLVE_INTERCEPTOR_ORDER - 1;
|
|
343
|
-
|
|
344
|
-
intercept(exchange: FetchExchange) {
|
|
345
|
-
const urlParams = exchange.ensureRequestUrlParams();
|
|
346
|
-
urlParams.path['ownerId'] = currentUserId;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// 注册拦截器
|
|
351
|
-
exampleFetcher.interceptors.request.use(new AppendOwnerId());
|
|
352
|
-
|
|
353
|
-
// 创建快照查询客户端
|
|
354
|
-
const cartSnapshotQueryClient = new SnapshotQueryClient<CartState>({
|
|
355
|
-
fetcher: exampleFetcher,
|
|
356
|
-
basePath: 'owner/{ownerId}/cart',
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
// 统计快照数量
|
|
360
|
-
const count = await cartSnapshotQueryClient.count(filter.matchAll());
|
|
361
|
-
|
|
362
|
-
// 列出快照
|
|
363
|
-
const listQuery: FilterListQuery = {
|
|
364
|
-
filter: filter.matchAll(),
|
|
365
|
-
};
|
|
366
|
-
const list = await cartSnapshotQueryClient.list(listQuery);
|
|
367
|
-
|
|
368
|
-
// 以流的形式列出快照
|
|
369
|
-
const listStream = await cartSnapshotQueryClient.listStream(listQuery);
|
|
370
|
-
for await (const event of listStream) {
|
|
371
|
-
const snapshot = event.data;
|
|
372
|
-
console.log('收到快照:', snapshot);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// 列出快照状态
|
|
376
|
-
const stateList = await cartSnapshotQueryClient.listState(listQuery);
|
|
377
|
-
|
|
378
|
-
// 以流的形式列出快照状态
|
|
379
|
-
const stateStream = await cartSnapshotQueryClient.listStateStream(listQuery);
|
|
380
|
-
for await (const event of stateStream) {
|
|
381
|
-
const state = event.data;
|
|
382
|
-
console.log('收到状态:', state);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// 分页查询快照
|
|
386
|
-
const pagedQuery: FilterPagedQuery = {
|
|
387
|
-
filter: filter.matchAll(),
|
|
388
|
-
};
|
|
389
|
-
const paged = await cartSnapshotQueryClient.paged(pagedQuery);
|
|
390
|
-
|
|
391
|
-
// 分页查询快照状态
|
|
392
|
-
const pagedState = await cartSnapshotQueryClient.pagedState(pagedQuery);
|
|
393
|
-
|
|
394
|
-
// 查询单个快照
|
|
395
|
-
const singleQuery: FilterSingleQuery = {
|
|
396
|
-
filter: filter.matchAll(),
|
|
397
|
-
};
|
|
398
|
-
const single = await cartSnapshotQueryClient.single(singleQuery);
|
|
399
|
-
|
|
400
|
-
// 查询单个快照状态
|
|
401
|
-
const singleState = await cartSnapshotQueryClient.singleState(singleQuery);
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
##### 方法
|
|
405
|
-
|
|
406
|
-
- `count(filter: FilterExpression): Promise<number>` - 统计匹配过滤表达式的快照数量。
|
|
407
|
-
- `list(listQuery: FilterListQuery): Promise<Partial<MaterializedSnapshot<S>>[]>` - 检索物化快照列表。
|
|
408
|
-
- `listStream(listQuery: FilterListQuery): Promise<ReadableStream<JsonServerSentEvent<Partial<MaterializedSnapshot<S>>>>>` -
|
|
409
|
-
以服务器发送事件的形式检索物化快照流。
|
|
410
|
-
- `listState(listQuery: FilterListQuery): Promise<Partial<S>[]>` - 检索快照状态列表。
|
|
411
|
-
- `listStateStream(listQuery: FilterListQuery): Promise<ReadableStream<JsonServerSentEvent<Partial<S>>>>` -
|
|
412
|
-
以服务器发送事件的形式检索快照状态流。
|
|
413
|
-
- `paged(pagedQuery: FilterPagedQuery): Promise<PagedList<Partial<MaterializedSnapshot<S>>>>` - 检索物化快照的分页列表。
|
|
414
|
-
- `pagedState(pagedQuery: FilterPagedQuery): Promise<PagedList<Partial<S>>>` - 检索快照状态的分页列表。
|
|
415
|
-
- `single(singleQuery: FilterSingleQuery): Promise<Partial<MaterializedSnapshot<S>>>` - 检索单个物化快照。
|
|
416
|
-
- `singleState(singleQuery: FilterSingleQuery): Promise<Partial<S>>` - 检索单个快照状态。
|
|
417
|
-
|
|
418
|
-
#### QueryClientFactory
|
|
419
|
-
|
|
420
|
-
用于创建预配置查询客户端的工厂。当您需要具有共享配置的多个客户端时,这非常有用。
|
|
421
|
-
|
|
422
|
-
```typescript
|
|
423
|
-
import {
|
|
424
|
-
filter,
|
|
425
|
-
QueryClientFactory,
|
|
426
|
-
ResourceAttributionPathSpec,
|
|
427
|
-
} from '@ahoo-wang/fetcher-wow';
|
|
428
|
-
import { idGenerator } from '@ahoo-wang/fetcher-cosec';
|
|
429
|
-
|
|
430
|
-
// 使用默认选项创建工厂
|
|
431
|
-
const factory = new QueryClientFactory({
|
|
432
|
-
contextAlias: 'example',
|
|
433
|
-
aggregateName: 'cart',
|
|
434
|
-
resourceAttribution: ResourceAttributionPathSpec.OWNER,
|
|
435
|
-
fetcher: exampleFetcher,
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
// 创建快照查询客户端
|
|
439
|
-
const snapshotClient = factory.createSnapshotQueryClient({
|
|
440
|
-
aggregateName: 'cart',
|
|
441
|
-
});
|
|
442
|
-
const carts = await snapshotClient.listState({ filter: filter.matchAll() });
|
|
443
|
-
|
|
444
|
-
// 创建状态聚合客户端
|
|
445
|
-
const stateClient = factory.createLoadStateAggregateClient({
|
|
446
|
-
aggregateName: 'cart',
|
|
447
|
-
});
|
|
448
|
-
const cart = await stateClient.load('cart-123');
|
|
449
|
-
|
|
450
|
-
// 创建事件流查询客户端
|
|
451
|
-
const eventClient = factory.createEventStreamQueryClient({
|
|
452
|
-
aggregateName: 'cart',
|
|
453
|
-
});
|
|
454
|
-
const events = await eventClient.list({ filter: filter.matchAll() });
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
**方法:**
|
|
458
|
-
|
|
459
|
-
- `createSnapshotQueryClient(options?: QueryClientOptions): SnapshotQueryClient` - 创建用于查询快照的客户端。
|
|
460
|
-
- `createLoadStateAggregateClient(options?: QueryClientOptions): LoadStateAggregateClient` - 创建用于按 ID 加载聚合状态的客户端。
|
|
461
|
-
- `createOwnerLoadStateAggregateClient(options?: QueryClientOptions): LoadOwnerStateAggregateClient` - 创建用于加载当前所有者聚合状态的客户端。
|
|
462
|
-
- `createEventStreamQueryClient(options?: QueryClientOptions): EventStreamQueryClient` - 创建用于查询事件流的客户端。
|
|
463
|
-
|
|
464
|
-
#### EventStreamQueryClient
|
|
465
|
-
|
|
466
|
-
用于查询领域事件流的客户端,支持全面的查询操作:
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
import {
|
|
470
|
-
Fetcher,
|
|
471
|
-
FetchExchange,
|
|
472
|
-
RequestInterceptor,
|
|
473
|
-
URL_RESOLVE_INTERCEPTOR_ORDER,
|
|
474
|
-
} from '@ahoo-wang/fetcher';
|
|
475
|
-
import '@ahoo-wang/fetcher-eventstream';
|
|
476
|
-
import {
|
|
477
|
-
EventStreamQueryClient,
|
|
478
|
-
filter,
|
|
479
|
-
FilterListQuery,
|
|
480
|
-
FilterPagedQuery,
|
|
481
|
-
} from '@ahoo-wang/fetcher-wow';
|
|
482
|
-
import { idGenerator } from '@ahoo-wang/fetcher-cosec';
|
|
483
|
-
|
|
484
|
-
// 使用基础配置创建 fetcher 实例
|
|
485
|
-
const exampleFetcher = new Fetcher({
|
|
486
|
-
baseURL: 'http://localhost:8080/',
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
// 定义当前用户 ID
|
|
490
|
-
const currentUserId = idGenerator.generateId();
|
|
491
|
-
|
|
492
|
-
// 创建处理 URL 参数的拦截器
|
|
493
|
-
class AppendOwnerId implements RequestInterceptor {
|
|
494
|
-
readonly name: string = 'AppendOwnerId';
|
|
495
|
-
readonly order: number = URL_RESOLVE_INTERCEPTOR_ORDER - 1;
|
|
496
|
-
|
|
497
|
-
intercept(exchange: FetchExchange) {
|
|
498
|
-
const urlParams = exchange.ensureRequestUrlParams();
|
|
499
|
-
urlParams.path['ownerId'] = currentUserId;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// 注册拦截器
|
|
504
|
-
exampleFetcher.interceptors.request.use(new AppendOwnerId());
|
|
505
|
-
|
|
506
|
-
// 创建事件流查询客户端
|
|
507
|
-
const cartEventStreamQueryClient = new EventStreamQueryClient({
|
|
508
|
-
fetcher: exampleFetcher,
|
|
509
|
-
basePath: 'owner/{ownerId}/cart',
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
// 统计事件流数量
|
|
513
|
-
const count = await cartEventStreamQueryClient.count(filter.matchAll());
|
|
514
|
-
|
|
515
|
-
// 列出事件流
|
|
516
|
-
const listQuery: FilterListQuery = {
|
|
517
|
-
filter: filter.matchAll(),
|
|
518
|
-
};
|
|
519
|
-
const list = await cartEventStreamQueryClient.list(listQuery);
|
|
520
|
-
|
|
521
|
-
// 以流的形式列出事件流
|
|
522
|
-
const listStream = await cartEventStreamQueryClient.listStream(listQuery);
|
|
523
|
-
for await (const event of listStream) {
|
|
524
|
-
const domainEventStream = event.data;
|
|
525
|
-
console.log('收到事件流:', domainEventStream);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// 分页查询事件流
|
|
529
|
-
const pagedQuery: FilterPagedQuery = {
|
|
530
|
-
filter: filter.matchAll(),
|
|
531
|
-
};
|
|
532
|
-
const paged = await cartEventStreamQueryClient.paged(pagedQuery);
|
|
9
|
+
pnpm add @ahoo-wang/fetcher @ahoo-wang/fetcher-decorator \
|
|
10
|
+
@ahoo-wang/fetcher-eventstream @ahoo-wang/fetcher-wow
|
|
533
11
|
```
|
|
534
12
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
- `count(filter: FilterExpression): Promise<number>` - 统计匹配过滤表达式的领域事件流数量。
|
|
538
|
-
- `list(listQuery: FilterListQuery): Promise<Partial<DomainEventStream>[]>` - 检索领域事件流列表。
|
|
539
|
-
- `listStream(listQuery: FilterListQuery): Promise<ReadableStream<JsonServerSentEvent<Partial<DomainEventStream>>>>` -
|
|
540
|
-
以服务器发送事件的形式检索领域事件流。
|
|
541
|
-
- `paged(pagedQuery: FilterPagedQuery): Promise<PagedList<Partial<DomainEventStream>>>` - 检索领域事件流的分页列表。
|
|
542
|
-
|
|
543
|
-
## 🛠️ 高级用法
|
|
13
|
+
Peer 依赖:`fetcher`、`fetcher-decorator` 和 `fetcher-eventstream`。
|
|
544
14
|
|
|
545
|
-
|
|
15
|
+
## 示例
|
|
546
16
|
|
|
547
|
-
```
|
|
548
|
-
import {
|
|
549
|
-
|
|
550
|
-
FetchExchange,
|
|
551
|
-
HttpMethod,
|
|
552
|
-
RequestInterceptor,
|
|
553
|
-
URL_RESOLVE_INTERCEPTOR_ORDER,
|
|
554
|
-
} from '@ahoo-wang/fetcher';
|
|
555
|
-
import '@ahoo-wang/fetcher-eventstream';
|
|
556
|
-
import {
|
|
557
|
-
CommandClient,
|
|
558
|
-
CommandRequest,
|
|
559
|
-
CommandHeaders,
|
|
560
|
-
CommandStage,
|
|
561
|
-
SnapshotQueryClient,
|
|
562
|
-
filter,
|
|
563
|
-
FilterListQuery,
|
|
564
|
-
} from '@ahoo-wang/fetcher-wow';
|
|
565
|
-
import { idGenerator } from '@ahoo-wang/fetcher-cosec';
|
|
566
|
-
|
|
567
|
-
interface CartItem {
|
|
568
|
-
productId: string;
|
|
569
|
-
quantity: number;
|
|
570
|
-
}
|
|
17
|
+
```ts
|
|
18
|
+
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
19
|
+
import { SnapshotQueryClient, filter, listQuery } from '@ahoo-wang/fetcher-wow';
|
|
571
20
|
|
|
572
21
|
interface CartState {
|
|
573
|
-
|
|
574
|
-
items: CartItem[];
|
|
22
|
+
status: 'ACTIVE' | 'CHECKED_OUT';
|
|
575
23
|
}
|
|
576
24
|
|
|
577
|
-
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
// 定义当前用户 ID
|
|
583
|
-
const currentUserId = idGenerator.generateId();
|
|
584
|
-
|
|
585
|
-
// 创建处理 URL 参数的拦截器
|
|
586
|
-
class AppendOwnerId implements RequestInterceptor {
|
|
587
|
-
readonly name: string = 'AppendOwnerId';
|
|
588
|
-
readonly order: number = URL_RESOLVE_INTERCEPTOR_ORDER - 1;
|
|
589
|
-
|
|
590
|
-
intercept(exchange: FetchExchange) {
|
|
591
|
-
const urlParams = exchange.ensureRequestUrlParams();
|
|
592
|
-
urlParams.path['ownerId'] = currentUserId;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// 注册拦截器
|
|
597
|
-
exampleFetcher.interceptors.request.use(new AppendOwnerId());
|
|
598
|
-
|
|
599
|
-
// 创建客户端
|
|
600
|
-
const cartCommandClient = new CommandClient({
|
|
601
|
-
fetcher: exampleFetcher,
|
|
602
|
-
basePath: 'owner/{ownerId}/cart',
|
|
25
|
+
const fetcher = new Fetcher({ baseURL: 'https://api.example.com' });
|
|
26
|
+
const snapshots = new SnapshotQueryClient<CartState>({
|
|
27
|
+
fetcher,
|
|
28
|
+
basePath: 'cart',
|
|
603
29
|
});
|
|
604
30
|
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// 定义命令接口
|
|
616
|
-
interface AddCartItem {
|
|
617
|
-
productId: string;
|
|
618
|
-
quantity: number;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
type AddCartItemCommand = CommandRequest<AddCartItem>;
|
|
622
|
-
|
|
623
|
-
// 1. 发送命令添加商品到购物车
|
|
624
|
-
const addItemCommand: AddCartItemCommand = {
|
|
625
|
-
method: HttpMethod.POST,
|
|
626
|
-
headers: {
|
|
627
|
-
[CommandHeaders.WAIT_STAGE]: CommandStage.SNAPSHOT,
|
|
628
|
-
},
|
|
629
|
-
body: {
|
|
630
|
-
productId: 'product-123',
|
|
631
|
-
quantity: 2,
|
|
632
|
-
},
|
|
633
|
-
};
|
|
634
|
-
|
|
635
|
-
const commandResult = await cartCommandClient.send(
|
|
636
|
-
CartCommandEndpoints.addCartItem,
|
|
637
|
-
addItemCommand,
|
|
31
|
+
const carts = await snapshots.listState(
|
|
32
|
+
listQuery({
|
|
33
|
+
filter: filter.and([
|
|
34
|
+
filter.ownerId('u-42'),
|
|
35
|
+
filter.eq('state.status', 'ACTIVE'),
|
|
36
|
+
]),
|
|
37
|
+
limit: 50,
|
|
38
|
+
}),
|
|
638
39
|
);
|
|
639
|
-
console.log('命令执行完成:', commandResult);
|
|
640
|
-
|
|
641
|
-
// 2. 查询更新后的购物车
|
|
642
|
-
const listQuery: FilterListQuery = {
|
|
643
|
-
filter: filter.matchAll(),
|
|
644
|
-
};
|
|
645
|
-
const carts = await cartSnapshotQueryClient.list(listQuery);
|
|
646
|
-
|
|
647
|
-
for (const cart of carts) {
|
|
648
|
-
console.log('购物车:', cart.state);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
// 3. 流式监听购物车更新
|
|
652
|
-
const listStream = await cartSnapshotQueryClient.listStream(listQuery);
|
|
653
|
-
for await (const event of listStream) {
|
|
654
|
-
const cart = event.data;
|
|
655
|
-
console.log('购物车更新:', cart.state);
|
|
656
|
-
}
|
|
657
40
|
```
|
|
658
41
|
|
|
659
|
-
##
|
|
660
|
-
|
|
661
|
-
```bash
|
|
662
|
-
# 运行测试
|
|
663
|
-
pnpm test
|
|
664
|
-
|
|
665
|
-
# 运行带覆盖率的测试
|
|
666
|
-
pnpm test --coverage
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
## 🤝 贡献
|
|
670
|
-
|
|
671
|
-
欢迎贡献!请查看
|
|
672
|
-
[贡献指南](https://github.com/Ahoo-Wang/fetcher/blob/main/wiki/guide/contributing.md) 获取更多详情。
|
|
42
|
+
## 核心能力
|
|
673
43
|
|
|
674
|
-
|
|
44
|
+
- 命令结果与流式等待阶段。
|
|
45
|
+
- 快照、领域事件、状态加载与所有者状态客户端。
|
|
46
|
+
- 提前校验的数组优先 `FilterExpression` 构建器。
|
|
47
|
+
- 单条、列表、分页、游标、计数与流查询契约。
|
|
48
|
+
- 投影、排序、嵌套聚合、建模、ABAC 与元数据类型。
|
|
675
49
|
|
|
676
|
-
|
|
50
|
+
## 文档
|
|
677
51
|
|
|
678
|
-
|
|
52
|
+
- [Wow CQRS 实战](https://fetcher.ahoo.me/zh/recipes/wow-cqrs)
|
|
53
|
+
- [Wow 参考](https://fetcher.ahoo.me/zh/reference/wow)
|
|
54
|
+
- [交互式查询 Story](https://fetcher.ahoo.me/storybook/)
|
|
679
55
|
|
|
680
|
-
|
|
681
|
-
<a href="https://github.com/Ahoo-Wang/fetcher">Fetcher</a> 生态系统的一部分
|
|
682
|
-
</p>
|
|
56
|
+
[English](./README.md) · [许可证](../../LICENSE)
|