@ahoo-wang/fetcher-wow
Version:
Support for Wow(https://github.com/Ahoo-Wang/Wow) in Fetcher
537 lines (432 loc) • 14.7 kB
Markdown
# @ahoo-wang/fetcher-wow
[](https://www.npmjs.com/package/@ahoo-wang/fetcher-wow)
[](https://github.com/Ahoo-Wang/fetcher/actions)
[](https://codecov.io/gh/Ahoo-Wang/fetcher)
[](https://github.com/Ahoo-Wang/fetcher/blob/main/LICENSE)
[](https://www.npmjs.com/package/@ahoo-wang/fetcher-wow)
[](https://www.npmjs.com/package/@ahoo-wang/fetcher-wow)
[](https://deepwiki.com/Ahoo-Wang/fetcher)
为 [Wow](https://github.com/Ahoo-Wang/Wow) 框架提供支持。提供用于与 Wow CQRS/DDD 框架配合使用的 TypeScript 类型和工具。
## 🌟 特性
- **📦 完整的 TypeScript 支持**:为所有 Wow 框架实体提供完整的类型定义,包括命令、事件和查询
- **🚀 命令客户端**:用于向 Wow 服务发送命令的高级客户端,支持同步和流式响应
- **🔍 强大的查询 DSL**:丰富的查询条件构建器,支持全面的操作符用于复杂查询
- **📡 实时事件流**:内置对服务器发送事件的支持,用于接收实时命令结果和数据更新
- **🔄 CQRS 模式实现**:对命令查询责任分离架构模式的一流支持
- **🧱 DDD 基础构件**:基本的领域驱动设计构建块,包括聚合、事件和值对象
- **🔍 查询客户端**:专门用于查询快照和事件流数据的客户端,支持全面的查询操作:
- 资源计数
- 资源列表查询
- 以服务器发送事件形式流式传输资源
- 资源分页
- 单个资源检索
## 🚀 快速开始
### 安装
```bash
# 使用 npm
npm install @ahoo-wang/fetcher-wow
# 使用 pnpm
pnpm add @ahoo-wang/fetcher-wow
# 使用 yarn
yarn add @ahoo-wang/fetcher-wow
```
## 📚 API 参考
### 命令模块
#### CommandResult
表示命令执行结果的接口:
```typescript
import { CommandResult, CommandStage } from '@ahoo-wang/fetcher-wow';
```
#### CommandClient
用于向 Wow 框架发送命令的 HTTP 客户端。该客户端提供了同步或流式接收命令结果的方法。
```typescript
import {
Fetcher,
FetchExchange,
RequestInterceptor,
URL_RESOLVE_INTERCEPTOR_ORDER,
} from '@ahoo-wang/fetcher';
import '@ahoo-wang/fetcher-eventstream';
import {
CommandClient,
CommandRequest,
HttpMethod,
CommandHttpHeaders,
CommandStage,
} from '@ahoo-wang/fetcher-wow';
import { idGenerator } from '@ahoo-wang/fetcher-cosec';
// 使用基础配置创建 fetcher 实例
const exampleFetcher = new Fetcher({
baseURL: 'http://localhost:8080/',
});
// 定义当前用户 ID
const currentUserId = idGenerator.generateId();
// 创建处理 URL 参数的拦截器
class AppendOwnerId implements RequestInterceptor {
readonly name: string = 'AppendOwnerId';
readonly order: number = URL_RESOLVE_INTERCEPTOR_ORDER - 1;
intercept(exchange: FetchExchange) {
const urlParams = exchange.ensureRequestUrlParams();
urlParams.path['ownerId'] = currentUserId;
}
}
// 注册拦截器
exampleFetcher.interceptors.request.use(new AppendOwnerId());
// 创建命令客户端
const cartCommandClient = new CommandClient({
fetcher: exampleFetcher,
basePath: 'owner/{ownerId}/cart',
});
// 定义命令端点
class CartCommandEndpoints {
static readonly addCartItem = 'add_cart_item';
}
// 定义命令接口
interface AddCartItem {
productId: string;
quantity: number;
}
type AddCartItemCommand = CommandRequest<AddCartItem>;
// 创建命令请求
const addCartItemCommand: AddCartItemCommand = {
method: HttpMethod.POST,
headers: {
[CommandHttpHeaders.WAIT_STAGE]: CommandStage.SNAPSHOT,
},
body: {
productId: 'productId',
quantity: 1,
},
};
// 发送命令并等待结果
const commandResult = await cartCommandClient.send(
CartCommandEndpoints.addCartItem,
addCartItemCommand,
);
// 发送命令并接收流式结果
const commandResultStream = await cartCommandClient.sendAndWaitStream(
CartCommandEndpoints.addCartItem,
addCartItemCommand,
);
for await (const commandResultEvent of commandResultStream) {
console.log('收到命令结果:', commandResultEvent.data);
}
```
##### 方法
- `send(path: string, commandRequest: CommandRequest): Promise<CommandResult>` - 发送命令并等待结果。
- `sendAndWaitStream(path: string, commandRequest: CommandRequest): Promise<CommandResultEventStream>` -
发送命令并以服务器发送事件的形式返回结果流。
### 查询模块
#### 条件构建器
支持操作符的综合查询条件构建器:
```typescript
import {
and,
or,
eq,
ne,
gt,
lt,
contains,
isIn,
between,
today,
active,
} from '@ahoo-wang/fetcher-wow';
// 简单条件
const simpleConditions = [
eq('name', 'John'),
ne('status', 'inactive'),
gt('age', 18),
lt('score', 100),
];
// 复杂条件
const complexCondition = and(
eq('tenantId', 'tenant-123'),
or(
contains('email', '@company.com'),
isIn('department', 'engineering', 'marketing'),
),
between('salary', 50000, 100000),
today('createdAt'),
active(),
);
// 日期条件
const dateConditions = [
today('createdAt'),
beforeToday('lastLogin', 7), // 最近7天内
thisWeek('updatedAt'),
lastMonth('createdDate'),
];
```
#### SnapshotQueryClient
用于查询物化快照的客户端,支持全面的查询操作:
```typescript
import {
Fetcher,
FetchExchange,
RequestInterceptor,
URL_RESOLVE_INTERCEPTOR_ORDER,
} from '@ahoo-wang/fetcher';
import '@ahoo-wang/fetcher-eventstream';
import {
SnapshotQueryClient,
all,
ListQuery,
PagedQuery,
SingleQuery,
} from '@ahoo-wang/fetcher-wow';
import { idGenerator } from '@ahoo-wang/fetcher-cosec';
interface CartItem {
productId: string;
quantity: number;
}
interface CartState extends Identifier {
items: CartItem[];
}
// 使用基础配置创建 fetcher 实例
const exampleFetcher = new Fetcher({
baseURL: 'http://localhost:8080/',
});
// 定义当前用户 ID
const currentUserId = idGenerator.generateId();
// 创建处理 URL 参数的拦截器
class AppendOwnerId implements RequestInterceptor {
readonly name: string = 'AppendOwnerId';
readonly order: number = URL_RESOLVE_INTERCEPTOR_ORDER - 1;
intercept(exchange: FetchExchange) {
const urlParams = exchange.ensureRequestUrlParams();
urlParams.path['ownerId'] = currentUserId;
}
}
// 注册拦截器
exampleFetcher.interceptors.request.use(new AppendOwnerId());
// 创建快照查询客户端
const cartSnapshotQueryClient = new SnapshotQueryClient<CartState>({
fetcher: exampleFetcher,
basePath: 'owner/{ownerId}/cart',
});
// 统计快照数量
const count = await cartSnapshotQueryClient.count(all());
// 列出快照
const listQuery: ListQuery = {
condition: all(),
};
const list = await cartSnapshotQueryClient.list(listQuery);
// 以流的形式列出快照
const listStream = await cartSnapshotQueryClient.listStream(listQuery);
for await (const event of listStream) {
const snapshot = event.data;
console.log('收到快照:', snapshot);
}
// 列出快照状态
const stateList = await cartSnapshotQueryClient.listState(listQuery);
// 以流的形式列出快照状态
const stateStream = await cartSnapshotQueryClient.listStateStream(listQuery);
for await (const event of stateStream) {
const state = event.data;
console.log('收到状态:', state);
}
// 分页查询快照
const pagedQuery: PagedQuery = {
condition: all(),
};
const paged = await cartSnapshotQueryClient.paged(pagedQuery);
// 分页查询快照状态
const pagedState = await cartSnapshotQueryClient.pagedState(pagedQuery);
// 查询单个快照
const singleQuery: SingleQuery = {
condition: all(),
};
const single = await cartSnapshotQueryClient.single(singleQuery);
// 查询单个快照状态
const singleState = await cartSnapshotQueryClient.singleState(singleQuery);
```
##### 方法
- `count(condition: Condition): Promise<number>` - 统计匹配给定条件的快照数量。
- `list(listQuery: ListQuery): Promise<Partial<MaterializedSnapshot<S>>[]>` - 检索物化快照列表。
- `listStream(listQuery: ListQuery): Promise<ReadableStream<JsonServerSentEvent<Partial<MaterializedSnapshot<S>>>>>` -
以服务器发送事件的形式检索物化快照流。
- `listState(listQuery: ListQuery): Promise<Partial<S>[]>` - 检索快照状态列表。
- `listStateStream(listQuery: ListQuery): Promise<ReadableStream<JsonServerSentEvent<Partial<S>>>>` -
以服务器发送事件的形式检索快照状态流。
- `paged(pagedQuery: PagedQuery): Promise<PagedList<Partial<MaterializedSnapshot<S>>>>` - 检索物化快照的分页列表。
- `pagedState(pagedQuery: PagedQuery): Promise<PagedList<Partial<S>>>` - 检索快照状态的分页列表。
- `single(singleQuery: SingleQuery): Promise<Partial<MaterializedSnapshot<S>>>` - 检索单个物化快照。
- `singleState(singleQuery: SingleQuery): Promise<Partial<S>>` - 检索单个快照状态。
#### EventStreamQueryClient
用于查询领域事件流的客户端,支持全面的查询操作:
```typescript
import {
Fetcher,
FetchExchange,
RequestInterceptor,
URL_RESOLVE_INTERCEPTOR_ORDER,
} from '@ahoo-wang/fetcher';
import '@ahoo-wang/fetcher-eventstream';
import {
EventStreamQueryClient,
all,
ListQuery,
PagedQuery,
} from '@ahoo-wang/fetcher-wow';
import { idGenerator } from '@ahoo-wang/fetcher-cosec';
// 使用基础配置创建 fetcher 实例
const exampleFetcher = new Fetcher({
baseURL: 'http://localhost:8080/',
});
// 定义当前用户 ID
const currentUserId = idGenerator.generateId();
// 创建处理 URL 参数的拦截器
class AppendOwnerId implements RequestInterceptor {
readonly name: string = 'AppendOwnerId';
readonly order: number = URL_RESOLVE_INTERCEPTOR_ORDER - 1;
intercept(exchange: FetchExchange) {
const urlParams = exchange.ensureRequestUrlParams();
urlParams.path['ownerId'] = currentUserId;
}
}
// 注册拦截器
exampleFetcher.interceptors.request.use(new AppendOwnerId());
// 创建事件流查询客户端
const cartEventStreamQueryClient = new EventStreamQueryClient({
fetcher: exampleFetcher,
basePath: 'owner/{ownerId}/cart',
});
// 统计事件流数量
const count = await cartEventStreamQueryClient.count(all());
// 列出事件流
const listQuery: ListQuery = {
condition: all(),
};
const list = await cartEventStreamQueryClient.list(listQuery);
// 以流的形式列出事件流
const listStream = await cartEventStreamQueryClient.listStream(listQuery);
for await (const event of listStream) {
const domainEventStream = event.data;
console.log('收到事件流:', domainEventStream);
}
// 分页查询事件流
const pagedQuery: PagedQuery = {
condition: all(),
};
const paged = await cartEventStreamQueryClient.paged(pagedQuery);
```
##### 方法
- `count(condition: Condition): Promise<number>` - 统计匹配给定条件的领域事件流数量。
- `list(listQuery: ListQuery): Promise<Partial<DomainEventStream>[]>` - 检索领域事件流列表。
- `listStream(listQuery: ListQuery): Promise<ReadableStream<JsonServerSentEvent<Partial<DomainEventStream>>>>` -
以服务器发送事件的形式检索领域事件流。
- `paged(pagedQuery: PagedQuery): Promise<PagedList<Partial<DomainEventStream>>>` - 检索领域事件流的分页列表。
## 🛠️ 高级用法
### 完整的命令和查询流程示例
```typescript
import {
Fetcher,
FetchExchange,
RequestInterceptor,
URL_RESOLVE_INTERCEPTOR_ORDER,
} from '@ahoo-wang/fetcher';
import '@ahoo-wang/fetcher-eventstream';
import {
CommandClient,
CommandRequest,
CommandHttpHeaders,
CommandStage,
HttpMethod,
SnapshotQueryClient,
all,
ListQuery,
} from '@ahoo-wang/fetcher-wow';
import { idGenerator } from '@ahoo-wang/fetcher-cosec';
interface CartItem {
productId: string;
quantity: number;
}
interface CartState {
id: string;
items: CartItem[];
}
// 创建 fetcher 实例
const exampleFetcher = new Fetcher({
baseURL: 'http://localhost:8080/',
});
// 定义当前用户 ID
const currentUserId = idGenerator.generateId();
// 创建处理 URL 参数的拦截器
class AppendOwnerId implements RequestInterceptor {
readonly name: string = 'AppendOwnerId';
readonly order: number = URL_RESOLVE_INTERCEPTOR_ORDER - 1;
intercept(exchange: FetchExchange) {
const urlParams = exchange.ensureRequestUrlParams();
urlParams.path['ownerId'] = currentUserId;
}
}
// 注册拦截器
exampleFetcher.interceptors.request.use(new AppendOwnerId());
// 创建客户端
const cartCommandClient = new CommandClient({
fetcher: exampleFetcher,
basePath: 'owner/{ownerId}/cart',
});
const cartSnapshotQueryClient = new SnapshotQueryClient<CartState>({
fetcher: exampleFetcher,
basePath: 'owner/{ownerId}/cart',
});
// 定义命令端点
class CartCommandEndpoints {
static readonly addCartItem = 'add_cart_item';
}
// 定义命令接口
interface AddCartItem {
productId: string;
quantity: number;
}
type AddCartItemCommand = CommandRequest<AddCartItem>;
// 1. 发送命令添加商品到购物车
const addItemCommand: AddCartItemCommand = {
method: HttpMethod.POST,
headers: {
[CommandHttpHeaders.WAIT_STAGE]: CommandStage.SNAPSHOT,
},
body: {
productId: 'product-123',
quantity: 2,
},
};
const commandResult = await cartCommandClient.send(
CartCommandEndpoints.addCartItem,
addItemCommand,
);
console.log('命令执行完成:', commandResult);
// 2. 查询更新后的购物车
const listQuery: ListQuery = {
condition: all(),
};
const carts = await cartSnapshotQueryClient.list(listQuery);
for (const cart of carts) {
console.log('购物车:', cart.state);
}
// 3. 流式监听购物车更新
const listStream = await cartSnapshotQueryClient.listStream(listQuery);
for await (const event of listStream) {
const cart = event.data;
console.log('购物车更新:', cart.state);
}
```
## 🧪 测试
```bash
# 运行测试
pnpm test
# 运行带覆盖率的测试
pnpm test --coverage
```
## 🤝 贡献
欢迎贡献!请查看
[贡献指南](https://github.com/Ahoo-Wang/fetcher/blob/main/CONTRIBUTING.md) 获取更多详情。
## 📄 许可证
Apache-2.0
---
<p align="center">
<a href="https://github.com/Ahoo-Wang/fetcher">Fetcher</a> 生态系统的一部分
</p>