@ahoo-wang/fetcher-wow 1.1.0 โ†’ 1.2.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.
Files changed (46) hide show
  1. package/README.md +275 -357
  2. package/README.zh-CN.md +283 -279
  3. package/dist/command/commandClient.d.ts +146 -0
  4. package/dist/command/commandClient.d.ts.map +1 -0
  5. package/dist/command/{commandHeaders.d.ts โ†’ commandHttpHeaders.d.ts} +6 -7
  6. package/dist/command/commandHttpHeaders.d.ts.map +1 -0
  7. package/dist/command/commandRequest.d.ts +26 -62
  8. package/dist/command/commandRequest.d.ts.map +1 -1
  9. package/dist/command/index.d.ts +2 -3
  10. package/dist/command/index.d.ts.map +1 -1
  11. package/dist/index.es.js +467 -226
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/index.umd.js +1 -1
  14. package/dist/index.umd.js.map +1 -1
  15. package/dist/query/event/domainEventStream.d.ts +49 -4
  16. package/dist/query/event/domainEventStream.d.ts.map +1 -1
  17. package/dist/query/event/eventStreamQueryApi.d.ts +6 -0
  18. package/dist/query/event/eventStreamQueryApi.d.ts.map +1 -1
  19. package/dist/query/event/eventStreamQueryClient.d.ts +46 -0
  20. package/dist/query/event/eventStreamQueryClient.d.ts.map +1 -0
  21. package/dist/query/event/index.d.ts +1 -0
  22. package/dist/query/event/index.d.ts.map +1 -1
  23. package/dist/query/queryApi.d.ts +62 -0
  24. package/dist/query/queryApi.d.ts.map +1 -1
  25. package/dist/query/snapshot/index.d.ts +1 -0
  26. package/dist/query/snapshot/index.d.ts.map +1 -1
  27. package/dist/query/snapshot/snapshot.d.ts +2 -2
  28. package/dist/query/snapshot/snapshot.d.ts.map +1 -1
  29. package/dist/query/snapshot/snapshotQueryApi.d.ts +28 -0
  30. package/dist/query/snapshot/snapshotQueryApi.d.ts.map +1 -1
  31. package/dist/query/snapshot/snapshotQueryClient.d.ts +74 -0
  32. package/dist/query/snapshot/snapshotQueryClient.d.ts.map +1 -0
  33. package/dist/query/sort.d.ts +4 -0
  34. package/dist/query/sort.d.ts.map +1 -1
  35. package/dist/types/client.d.ts +15 -0
  36. package/dist/types/client.d.ts.map +1 -0
  37. package/dist/types/endpoints.d.ts +28 -0
  38. package/dist/types/endpoints.d.ts.map +1 -0
  39. package/dist/types/index.d.ts +1 -0
  40. package/dist/types/index.d.ts.map +1 -1
  41. package/package.json +4 -4
  42. package/dist/command/commandHeaders.d.ts.map +0 -1
  43. package/dist/command/commandHttpClient.d.ts +0 -133
  44. package/dist/command/commandHttpClient.d.ts.map +0 -1
  45. package/dist/command/commandHttpRequest.d.ts +0 -48
  46. package/dist/command/commandHttpRequest.d.ts.map +0 -1
package/README.md CHANGED
@@ -8,16 +8,23 @@
8
8
  [![npm bundle size](https://img.shields.io/bundlephobia/minzip/%40ahoo-wang%2Ffetcher-wow)](https://www.npmjs.com/package/@ahoo-wang/fetcher-wow)
9
9
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/Ahoo-Wang/fetcher)
10
10
 
11
- Provides TypeScript types and utilities for working with the [Wow](https://github.com/Ahoo-Wang/Wow) CQRS/DDD framework.
11
+ Support for [Wow](https://github.com/Ahoo-Wang/Wow) framework in Fetcher. Provides TypeScript types and utilities for
12
+ working with the Wow CQRS/DDD framework.
12
13
 
13
14
  ## ๐ŸŒŸ Features
14
15
 
15
- - **๐Ÿ“ฆ Comprehensive Type Definitions**: Full TypeScript support for Wow framework entities
16
- - **๐Ÿ”ง Command Utilities**: Helpers for working with Wow commands and command results
17
- - **๐Ÿ” Query DSL**: Rich query condition builders with operator support
18
- - **๐Ÿ“ก Event Stream Support**: Integration with Server-Sent Events for real-time command results
19
- - **๐Ÿ”„ CQRS Pattern**: Support for Command Query Responsibility Segregation patterns
20
- - **๐Ÿงฑ DDD Building Blocks**: Domain-driven design types for aggregates, events, and more
16
+ - **๐Ÿ”„ CQRS Pattern Implementation**: First-class support for Command Query Responsibility Segregation architectural
17
+ pattern
18
+ - **๐Ÿงฑ DDD Primitives**: Essential Domain-Driven Design building blocks including aggregates, events, and value objects
19
+ - **๐Ÿ“ฆ Complete TypeScript Support**: Full type definitions for all Wow framework entities including commands, events,
20
+ and queries
21
+ - **๐Ÿ“ก Real-time Event Streaming**: Built-in support for Server-Sent Events to receive real-time command results and data
22
+ updates
23
+ - **๐Ÿš€ Command Client**: High-level client for sending commands to Wow services with both synchronous and streaming
24
+ responses
25
+ - **๐Ÿ” Powerful Query DSL**: Rich query condition builder with comprehensive operator support for complex querying
26
+ - **๐Ÿ” Query Clients**: Specialized clients for querying snapshot and event stream data with comprehensive query
27
+ operations
21
28
 
22
29
  ## ๐Ÿš€ Quick Start
23
30
 
@@ -38,61 +45,6 @@ yarn add @ahoo-wang/fetcher-wow
38
45
 
39
46
  ### Command Module
40
47
 
41
- #### CommandHeaders
42
-
43
- Constants for standard HTTP headers used in Wow command processing:
44
-
45
- ```typescript
46
- import { CommandHeaders } from '@ahoo-wang/fetcher-wow';
47
-
48
- // Example usage
49
- const request = {
50
- method: 'POST',
51
- headers: {
52
- [CommandHeaders.TENANT_ID]: 'tenant-123',
53
- [CommandHeaders.AGGREGATE_ID]: 'aggregate-456',
54
- [CommandHeaders.REQUEST_ID]: 'request-789',
55
- },
56
- body: command,
57
- };
58
- ```
59
-
60
- Key headers include:
61
-
62
- - `TENANT_ID` - Tenant identifier
63
- - `OWNER_ID` - Owner identifier
64
- - `AGGREGATE_ID` - Aggregate root identifier
65
- - `AGGREGATE_VERSION` - Expected aggregate version
66
- - `REQUEST_ID` - Request tracking ID
67
- - `WAIT_*` - Various wait condition headers
68
- - `LOCAL_FIRST` - Local processing preference
69
- - And many more...
70
-
71
- #### CommandRequest
72
-
73
- Interface for command requests with full configuration options:
74
-
75
- ```typescript
76
- import { CommandRequest, CommandHeaders } from '@ahoo-wang/fetcher-wow';
77
-
78
- const commandRequest: CommandRequest = {
79
- path: '/commands/CreateUser',
80
- method: 'POST',
81
- headers: {
82
- [CommandHeaders.TENANT_ID]: 'tenant-123',
83
- },
84
- body: {
85
- name: 'John Doe',
86
- email: 'john@example.com',
87
- },
88
- timeout: 5000,
89
- aggregateId: 'user-456',
90
- requestId: 'req-789',
91
- localFirst: true,
92
- stream: false,
93
- };
94
- ```
95
-
96
48
  #### CommandResult
97
49
 
98
50
  Interface representing the result of command execution:
@@ -101,56 +53,55 @@ Interface representing the result of command execution:
101
53
  import { CommandResult, CommandStage } from '@ahoo-wang/fetcher-wow';
102
54
  ```
103
55
 
104
- #### CommandHttpClient
56
+ #### CommandClient
105
57
 
106
- HTTP client for sending commands to the Wow framework. This client provides methods to send commands and receive results
58
+ HTTP client for sending commands to the Wow framework. The client provides methods to send commands and receive results
107
59
  either synchronously or as a stream of events.
108
60
 
109
- ##### Usage
110
-
111
- First, create a fetcher instance with base configuration and add the EventStreamInterceptor:
112
-
113
61
  ```typescript
114
- import { Fetcher } from '@ahoo-wang/fetcher';
115
- import { EventStreamInterceptor } from '@ahoo-wang/fetcher-eventstream';
62
+ import { Fetcher, URL_RESOLVE_INTERCEPTOR_ORDER } from '@ahoo-wang/fetcher';
63
+ import '@ahoo-wang/fetcher-eventstream';
64
+ import {
65
+ CommandClient,
66
+ CommandRequest,
67
+ HttpMethod,
68
+ CommandHttpHeaders,
69
+ CommandStage,
70
+ } from '@ahoo-wang/fetcher-wow';
71
+ import { idGenerator } from '@ahoo-wang/fetcher-cosec';
116
72
 
73
+ // Create a fetcher instance
117
74
  const wowFetcher = new Fetcher({
118
75
  baseURL: 'http://localhost:8080/',
119
76
  });
120
77
 
121
- // Add EventStreamInterceptor to handle Server-Sent Events
122
- wowFetcher.interceptors.response.use(new EventStreamInterceptor());
123
- ```
124
-
125
- Then create a CommandHttpClient instance:
126
-
127
- ```typescript
128
- import { CommandHttpClient } from '@ahoo-wang/fetcher-wow';
129
-
130
- const commandHttpClient = new CommandHttpClient(wowFetcher);
131
- ```
132
-
133
- ##### Sending a Command
134
-
135
- To send a command and wait for the result:
78
+ // Add interceptor to handle URL parameters
79
+ const ownerId = idGenerator.generateId();
80
+ wowFetcher.interceptors.request.use({
81
+ name: 'AppendOwnerId',
82
+ order: URL_RESOLVE_INTERCEPTOR_ORDER - 1,
83
+ intercept(exchange) {
84
+ exchange.request.urlParams = {
85
+ path: {
86
+ ...exchange.request.urlParams?.path,
87
+ ownerId,
88
+ },
89
+ query: exchange.request.urlParams?.query,
90
+ };
91
+ },
92
+ });
136
93
 
137
- ```typescript
138
- import {
139
- CommandHeaders,
140
- CommandStage,
141
- HttpMethod,
142
- } from '@ahoo-wang/fetcher-wow';
94
+ // Create the command client
95
+ const commandClient = new CommandClient({
96
+ fetcher: wowFetcher,
97
+ basePath: 'owner/{ownerId}/cart',
98
+ });
143
99
 
144
- const command = {
145
- path: 'owner/{ownerId}/cart/add_cart_item',
100
+ // Define a command request
101
+ const command: CommandRequest = {
146
102
  method: HttpMethod.POST,
147
103
  headers: {
148
- [CommandHeaders.WAIT_STAGE]: CommandStage.SNAPSHOT,
149
- },
150
- urlParams: {
151
- path: {
152
- ownerId: 'ownerId',
153
- },
104
+ [CommandHttpHeaders.WAIT_STAGE]: CommandStage.SNAPSHOT,
154
105
  },
155
106
  body: {
156
107
  productId: 'productId',
@@ -158,74 +109,25 @@ const command = {
158
109
  },
159
110
  };
160
111
 
161
- const result = await commandHttpClient.send(command);
162
- console.log('Command result:', result);
163
- ```
164
-
165
- ##### Sending a Command and Receiving Stream Results
166
-
167
- For long-running commands, you can receive results as a stream of events:
112
+ // Send command and wait for result
113
+ const commandResult = await commandClient.send('add_cart_item', command);
168
114
 
169
- ```typescript
170
- const commandResultStream = await commandHttpClient.sendAndWaitStream(command);
115
+ // Send command and receive results as a stream of events
116
+ const commandResultStream = await commandClient.sendAndWaitStream(
117
+ 'add_cart_item',
118
+ command,
119
+ );
171
120
  for await (const commandResultEvent of commandResultStream) {
172
- console.log('Received:', commandResultEvent.data);
121
+ console.log('Received command result:', commandResultEvent.data);
173
122
  }
174
123
  ```
175
124
 
176
- ##### API
177
-
178
- **Constructor**
179
-
180
- ```typescript
181
- new CommandHttpClient(fetcher
182
- :
183
- Fetcher
184
- )
185
- ```
186
-
187
- Creates a new CommandHttpClient instance.
188
-
189
- - `fetcher` - The Fetcher instance to use for HTTP requests
190
-
191
- **send**
192
-
193
- ```typescript
194
- send(commandHttpRequest
195
- :
196
- CommandHttpRequest
197
- ):
198
- Promise<CommandResult>
199
- ```
200
-
201
- Sends a command and waits for the result. This method sends a command to the Wow framework and waits for the processing
202
- to complete before returning the result.
203
-
204
- **sendAndWaitStream**
205
-
206
- ```typescript
207
- sendAndWaitStream(commandHttpRequest
208
- :
209
- CommandHttpRequest
210
- ):
211
- Promise<CommandResultEventStream>
212
- ```
213
-
214
- Sends a command and returns a stream of results as events. This method sets the Accept header to text/event-stream to
215
- receive Server-Sent Events, and uses a JSON event stream result extractor to parse the response. It's useful for
216
- long-running commands where you want to receive progress updates or multiple results.
217
-
218
- #### CommandResultEventStream
125
+ ##### Methods
219
126
 
220
- Type alias for a readable stream of CommandResult events:
221
-
222
- ```typescript
223
- import { CommandResult } from '@ahoo-wang/fetcher-wow';
224
- import { JsonServerSentEventStream } from '@ahoo-wang/fetcher-eventstream';
225
-
226
- // CommandResultEventStream is a JsonServerSentEventStream of CommandResult
227
- type CommandResultEventStream = JsonServerSentEventStream<CommandResult>;
228
- ```
127
+ - `send(path: string, commandRequest: CommandRequest): Promise<CommandResult>` - Sends a command and waits for the
128
+ result.
129
+ - `sendAndWaitStream(path: string, commandRequest: CommandRequest): Promise<CommandResultEventStream>` - Sends a command
130
+ and returns a stream of results as Server-Sent Events.
229
131
 
230
132
  ### Query Module
231
133
 
@@ -277,245 +179,261 @@ const dateConditions = [
277
179
  ];
278
180
  ```
279
181
 
280
- #### Operators
182
+ #### SnapshotQueryClient
281
183
 
282
- Full operator enumeration for query building:
184
+ Client for querying materialized snapshots:
283
185
 
284
186
  ```typescript
285
- import { Operator } from '@ahoo-wang/fetcher-wow';
286
-
287
- // Logical operators
288
- (Operator.AND, Operator.OR, Operator.NOR);
289
-
290
- // Comparison operators
291
- (Operator.EQ,
292
- Operator.NE,
293
- Operator.GT,
294
- Operator.LT,
295
- Operator.GTE,
296
- Operator.LTE);
297
-
298
- // Membership operators
299
- (Operator.IN, Operator.NOT_IN, Operator.ALL_IN, Operator.BETWEEN);
300
-
301
- // String operators
302
- (Operator.CONTAINS, Operator.STARTS_WITH, Operator.ENDS_WITH);
303
-
304
- // Existence operators
305
- (Operator.NULL, Operator.NOT_NULL, Operator.EXISTS);
306
-
307
- // Boolean operators
308
- (Operator.TRUE, Operator.FALSE);
309
-
310
- // Date operators
311
- (Operator.TODAY,
312
- Operator.BEFORE_TODAY,
313
- Operator.TOMORROW,
314
- Operator.THIS_WEEK,
315
- Operator.NEXT_WEEK,
316
- Operator.LAST_WEEK,
317
- Operator.THIS_MONTH,
318
- Operator.LAST_MONTH,
319
- Operator.RECENT_DAYS,
320
- Operator.EARLIER_DAYS);
321
-
322
- // Special operators
323
- (Operator.ID,
324
- Operator.IDS,
325
- Operator.AGGREGATE_ID,
326
- Operator.AGGREGATE_IDS,
327
- Operator.TENANT_ID,
328
- Operator.OWNER_ID,
329
- Operator.DELETED,
330
- Operator.ALL,
331
- Operator.ELEM_MATCH,
332
- Operator.RAW);
333
- ```
187
+ import { Fetcher, URL_RESOLVE_INTERCEPTOR_ORDER } from '@ahoo-wang/fetcher';
188
+ import '@ahoo-wang/fetcher-eventstream';
189
+ import {
190
+ SnapshotQueryClient,
191
+ all,
192
+ ListQuery,
193
+ PagedQuery,
194
+ SingleQuery,
195
+ } from '@ahoo-wang/fetcher-wow';
196
+ import { idGenerator } from '@ahoo-wang/fetcher-cosec';
334
197
 
335
- #### Queryable Interface
198
+ interface CartItem {
199
+ productId: string;
200
+ quantity: number;
201
+ }
336
202
 
337
- Interfaces for building queries with sorting, pagination, and projection:
203
+ interface CartState {
204
+ id: string;
205
+ items: CartItem[];
206
+ }
338
207
 
339
- ```typescript
340
- import {
341
- Queryable,
342
- SortDirection,
343
- DEFAULT_PAGINATION,
344
- } from '@ahoo-wang/fetcher-wow';
208
+ // Create a fetcher instance
209
+ const wowFetcher = new Fetcher({
210
+ baseURL: 'http://localhost:8080/',
211
+ });
345
212
 
346
- const query: Queryable = {
347
- condition: eq('status', 'active'),
348
- sort: [
349
- { field: 'createdAt', direction: SortDirection.DESC },
350
- { field: 'name', direction: SortDirection.ASC },
351
- ],
352
- projection: {
353
- include: ['id', 'name', 'email', 'status'],
354
- exclude: ['password', 'internalNotes'],
213
+ // Add interceptor to handle URL parameters
214
+ const ownerId = idGenerator.generateId();
215
+ wowFetcher.interceptors.request.use({
216
+ name: 'AppendOwnerId',
217
+ order: URL_RESOLVE_INTERCEPTOR_ORDER - 1,
218
+ intercept(exchange) {
219
+ exchange.request.urlParams = {
220
+ path: {
221
+ ...exchange.request.urlParams?.path,
222
+ ownerId,
223
+ },
224
+ query: exchange.request.urlParams?.query,
225
+ };
355
226
  },
356
- };
227
+ });
357
228
 
358
- const pagedQuery = {
359
- ...query,
360
- pagination: {
361
- index: 2,
362
- size: 20,
363
- },
364
- };
365
- ```
229
+ // Create the snapshot query client
230
+ const snapshotQueryClient = new SnapshotQueryClient<CartState>({
231
+ fetcher: wowFetcher,
232
+ basePath: 'owner/{ownerId}/cart',
233
+ });
366
234
 
367
- ### Types Module
235
+ // Count snapshots
236
+ const count = await snapshotQueryClient.count(all());
368
237
 
369
- #### Core Types
238
+ // List snapshots
239
+ const listQuery: ListQuery = {
240
+ condition: all(),
241
+ };
242
+ const list = await snapshotQueryClient.list(listQuery);
370
243
 
371
- Essential types for domain modeling:
244
+ // List snapshots as stream
245
+ const listStream = await snapshotQueryClient.listStream(listQuery);
246
+ for await (const event of listStream) {
247
+ const snapshot = event.data;
248
+ console.log('Received snapshot:', snapshot);
249
+ }
372
250
 
373
- ```typescript
374
- import {
375
- Identifier,
376
- Version,
377
- TenantId,
378
- OwnerId,
379
- NamedAggregate,
380
- AggregateId,
381
- StateCapable,
382
- } from '@ahoo-wang/fetcher-wow';
251
+ // List snapshot states
252
+ const stateList = await snapshotQueryClient.listState(listQuery);
383
253
 
384
- interface User
385
- extends Identifier,
386
- Version,
387
- TenantId,
388
- OwnerId,
389
- NamedAggregate,
390
- StateCapable<UserState> {
391
- id: string;
392
- version: number;
393
- tenantId: string;
394
- ownerId: string;
395
- contextName: string;
396
- aggregateName: string;
397
- state: UserState;
254
+ // List snapshot states as stream
255
+ const stateStream = await snapshotQueryClient.listStateStream(listQuery);
256
+ for await (const event of stateStream) {
257
+ const state = event.data;
258
+ console.log('Received state:', state);
398
259
  }
399
260
 
400
- interface UserState {
401
- name: string;
402
- email: string;
403
- status: 'active' | 'inactive';
404
- createdAt: number;
405
- }
261
+ // Paged snapshots
262
+ const pagedQuery: PagedQuery = {
263
+ condition: all(),
264
+ };
265
+ const paged = await snapshotQueryClient.paged(pagedQuery);
266
+
267
+ // Paged snapshot states
268
+ const pagedState = await snapshotQueryClient.pagedState(pagedQuery);
269
+
270
+ // Single snapshot
271
+ const singleQuery: SingleQuery = {
272
+ condition: all(),
273
+ };
274
+ const single = await snapshotQueryClient.single(singleQuery);
275
+
276
+ // Single snapshot state
277
+ const singleState = await snapshotQueryClient.singleState(singleQuery);
406
278
  ```
407
279
 
408
- #### Error Handling
280
+ #### EventStreamQueryClient
409
281
 
410
- Standard error types and codes:
282
+ Client for querying domain event streams:
411
283
 
412
284
  ```typescript
413
- import { ErrorInfo, ErrorCodes, RecoverableType } from '@ahoo-wang/fetcher-wow';
285
+ import { Fetcher, URL_RESOLVE_INTERCEPTOR_ORDER } from '@ahoo-wang/fetcher';
286
+ import '@ahoo-wang/fetcher-eventstream';
287
+ import {
288
+ EventStreamQueryClient,
289
+ all,
290
+ ListQuery,
291
+ PagedQuery,
292
+ } from '@ahoo-wang/fetcher-wow';
293
+ import { idGenerator } from '@ahoo-wang/fetcher-cosec';
414
294
 
415
- const errorInfo: ErrorInfo = {
416
- errorCode: ErrorCodes.NOT_FOUND,
417
- errorMsg: 'User not found',
418
- bindingErrors: [],
419
- };
295
+ // Create a fetcher instance
296
+ const wowFetcher = new Fetcher({
297
+ baseURL: 'http://localhost:8080/',
298
+ });
420
299
 
421
- // Check error types
422
- if (ErrorCodes.isSucceeded(errorInfo.errorCode)) {
423
- console.log('Operation successful');
424
- } else {
425
- console.error('Operation failed:', errorInfo.errorMsg);
426
- }
427
- ```
300
+ // Add interceptor to handle URL parameters
301
+ const ownerId = idGenerator.generateId();
302
+ wowFetcher.interceptors.request.use({
303
+ name: 'AppendOwnerId',
304
+ order: URL_RESOLVE_INTERCEPTOR_ORDER - 1,
305
+ intercept(exchange) {
306
+ exchange.request.urlParams = {
307
+ path: {
308
+ ...exchange.request.urlParams?.path,
309
+ ownerId,
310
+ },
311
+ query: exchange.request.urlParams?.query,
312
+ };
313
+ },
314
+ });
428
315
 
429
- #### Function Types
316
+ // Create the event stream query client
317
+ const eventStreamQueryClient = new EventStreamQueryClient({
318
+ fetcher: wowFetcher,
319
+ basePath: 'owner/{ownerId}/cart',
320
+ });
430
321
 
431
- Function information for event and command handlers:
322
+ // Count event streams
323
+ const count = await eventStreamQueryClient.count(all());
432
324
 
433
- ```typescript
434
- import { FunctionInfo, FunctionKind } from '@ahoo-wang/fetcher-wow';
325
+ // List event streams
326
+ const listQuery: ListQuery = {
327
+ condition: all(),
328
+ };
329
+ const list = await eventStreamQueryClient.list(listQuery);
435
330
 
436
- const functionInfo: FunctionInfo = {
437
- functionKind: FunctionKind.COMMAND,
438
- contextName: 'user-context',
439
- processorName: 'UserProcessor',
440
- name: 'CreateUser',
331
+ // List event streams as stream
332
+ const listStream = await eventStreamQueryClient.listStream(listQuery);
333
+ for await (const event of listStream) {
334
+ const domainEventStream = event.data;
335
+ console.log('Received event stream:', domainEventStream);
336
+ }
337
+
338
+ // Paged event streams
339
+ const pagedQuery: PagedQuery = {
340
+ condition: all(),
441
341
  };
342
+ const paged = await eventStreamQueryClient.paged(pagedQuery);
442
343
  ```
443
344
 
444
345
  ## ๐Ÿ› ๏ธ Advanced Usage
445
346
 
446
- ### Complex Query Building
347
+ ### Complete Example with Command and Query Flow
447
348
 
448
349
  ```typescript
350
+ import { Fetcher, URL_RESOLVE_INTERCEPTOR_ORDER } from '@ahoo-wang/fetcher';
351
+ import '@ahoo-wang/fetcher-eventstream';
449
352
  import {
450
- and,
451
- or,
452
- eq,
453
- ne,
454
- gt,
455
- lt,
456
- contains,
457
- isIn,
458
- notIn,
459
- between,
460
- startsWith,
461
- endsWith,
462
- elemMatch,
463
- isNull,
464
- notNull,
465
- exists,
466
- today,
467
- thisWeek,
468
- recentDays,
353
+ CommandClient,
354
+ CommandRequest,
355
+ CommandHttpHeaders,
356
+ CommandStage,
357
+ HttpMethod,
358
+ SnapshotQueryClient,
359
+ all,
360
+ ListQuery,
469
361
  } from '@ahoo-wang/fetcher-wow';
362
+ import { idGenerator } from '@ahoo-wang/fetcher-cosec';
470
363
 
471
- // Build a complex query for user search
472
- const userSearchQuery = {
473
- condition: and(
474
- eq('tenantId', 'tenant-123'),
475
- ne('status', 'deleted'),
476
- or(
477
- // Search by name or email
478
- contains('name', 'john'),
479
- contains('email', 'john'),
480
- ),
481
- // Age and score filters
482
- gt('age', 18),
483
- between('score', 50, 100),
484
-
485
- // Department filters
486
- isIn('departments', 'engineering', 'marketing'),
487
- notIn('blockedDepartments', 'hr', 'finance'),
488
-
489
- // String pattern matching
490
- startsWith('employeeId', 'EMP-'),
491
- endsWith('domain', '.com'),
492
-
493
- // Array matching
494
- elemMatch('roles', eq('name', 'admin')),
495
-
496
- // Date filters
497
- recentDays('lastLogin', 30),
498
- thisWeek('createdAt'),
499
-
500
- // Existence checks
501
- exists('phoneNumber'),
502
- notNull('address'),
503
- ),
364
+ interface CartItem {
365
+ productId: string;
366
+ quantity: number;
367
+ }
368
+
369
+ interface CartState {
370
+ id: string;
371
+ items: CartItem[];
372
+ }
504
373
 
505
- sort: [
506
- { field: 'score', direction: 'DESC' },
507
- { field: 'lastLogin', direction: 'DESC' },
508
- ],
374
+ // Create a fetcher instance
375
+ const wowFetcher = new Fetcher({
376
+ baseURL: 'http://localhost:8080/',
377
+ });
509
378
 
510
- projection: {
511
- include: ['id', 'name', 'email', 'score', 'lastLogin', 'departments'],
379
+ // Add interceptor to handle URL parameters
380
+ const ownerId = idGenerator.generateId();
381
+ wowFetcher.interceptors.request.use({
382
+ name: 'AppendOwnerId',
383
+ order: URL_RESOLVE_INTERCEPTOR_ORDER - 1,
384
+ intercept(exchange) {
385
+ exchange.request.urlParams = {
386
+ path: {
387
+ ...exchange.request.urlParams?.path,
388
+ ownerId,
389
+ },
390
+ query: exchange.request.urlParams?.query,
391
+ };
512
392
  },
393
+ });
394
+
395
+ // Create clients
396
+ const commandClient = new CommandClient({
397
+ fetcher: wowFetcher,
398
+ basePath: 'owner/{ownerId}/cart',
399
+ });
400
+
401
+ const snapshotQueryClient = new SnapshotQueryClient<CartState>({
402
+ fetcher: wowFetcher,
403
+ basePath: 'owner/{ownerId}/cart',
404
+ });
513
405
 
514
- pagination: {
515
- index: 1,
516
- size: 50,
406
+ // 1. Send command to add item to cart
407
+ const addItemCommand: CommandRequest = {
408
+ method: HttpMethod.POST,
409
+ headers: {
410
+ [CommandHttpHeaders.WAIT_STAGE]: CommandStage.SNAPSHOT,
517
411
  },
412
+ body: {
413
+ productId: 'product-123',
414
+ quantity: 2,
415
+ },
416
+ };
417
+
418
+ const commandResult = await commandClient.send('add_cart_item', addItemCommand);
419
+ console.log('Command executed:', commandResult);
420
+
421
+ // 2. Query the updated cart
422
+ const listQuery: ListQuery = {
423
+ condition: all(),
518
424
  };
425
+ const carts = await snapshotQueryClient.list(listQuery);
426
+
427
+ for (const cart of carts) {
428
+ console.log('Cart:', cart.state);
429
+ }
430
+
431
+ // 3. Stream cart updates
432
+ const listStream = await snapshotQueryClient.listStream(listQuery);
433
+ for await (const event of listStream) {
434
+ const cart = event.data;
435
+ console.log('Cart updated:', cart.state);
436
+ }
519
437
  ```
520
438
 
521
439
  ## ๐Ÿงช Testing