@ahoo-wang/fetcher-wow 3.17.0 → 3.18.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 (39) hide show
  1. package/README.md +150 -62
  2. package/README.zh-CN.md +133 -51
  3. package/dist/index.cjs.js +1 -1
  4. package/dist/index.cjs.js.map +1 -1
  5. package/dist/index.es.js +1014 -355
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/query/aggregation.d.ts +131 -0
  8. package/dist/query/aggregation.d.ts.map +1 -0
  9. package/dist/query/condition.d.ts +52 -0
  10. package/dist/query/condition.d.ts.map +1 -1
  11. package/dist/query/cursorQuery.d.ts +10 -1
  12. package/dist/query/cursorQuery.d.ts.map +1 -1
  13. package/dist/query/event/eventStreamQueryClient.d.ts +21 -21
  14. package/dist/query/event/eventStreamQueryClient.d.ts.map +1 -1
  15. package/dist/query/filter.d.ts +224 -0
  16. package/dist/query/filter.d.ts.map +1 -0
  17. package/dist/query/index.d.ts +2 -0
  18. package/dist/query/index.d.ts.map +1 -1
  19. package/dist/query/locale/en_US.cjs.js.map +1 -1
  20. package/dist/query/locale/en_US.d.ts +1 -0
  21. package/dist/query/locale/en_US.d.ts.map +1 -1
  22. package/dist/query/locale/en_US.es.js.map +1 -1
  23. package/dist/query/locale/operatorLocale.d.ts +1 -0
  24. package/dist/query/locale/operatorLocale.d.ts.map +1 -1
  25. package/dist/query/locale/zh_CN.cjs.js.map +1 -1
  26. package/dist/query/locale/zh_CN.d.ts +1 -0
  27. package/dist/query/locale/zh_CN.d.ts.map +1 -1
  28. package/dist/query/locale/zh_CN.es.js.map +1 -1
  29. package/dist/query/operator.d.ts +3 -0
  30. package/dist/query/operator.d.ts.map +1 -1
  31. package/dist/query/queryApi.d.ts +9 -8
  32. package/dist/query/queryApi.d.ts.map +1 -1
  33. package/dist/query/queryable.d.ts +34 -4
  34. package/dist/query/queryable.d.ts.map +1 -1
  35. package/dist/query/snapshot/snapshotQueryApi.d.ts +12 -5
  36. package/dist/query/snapshot/snapshotQueryApi.d.ts.map +1 -1
  37. package/dist/query/snapshot/snapshotQueryClient.d.ts +46 -42
  38. package/dist/query/snapshot/snapshotQueryClient.d.ts.map +1 -1
  39. package/package.json +9 -8
package/README.md CHANGED
@@ -23,7 +23,8 @@ working with the Wow CQRS/DDD framework.
23
23
  updates
24
24
  - **🚀 Command Client**: High-level client for sending commands to Wow services with both synchronous and streaming
25
25
  responses
26
- - **🔍 Powerful Query DSL**: Rich query condition builder with comprehensive operator support for complex querying
26
+ - **🔍 Powerful Query DSL**: Typed `FilterExpression` builders with comprehensive operator support
27
+ - **📊 Snapshot Aggregation**: Group and aggregate snapshot data with typed queries
27
28
  - **🔍 Query Clients**: Specialized clients for querying snapshot and event stream data with comprehensive query
28
29
  operations:
29
30
  - Counting resources
@@ -68,6 +69,7 @@ either synchronously or as a stream of events.
68
69
  import {
69
70
  Fetcher,
70
71
  FetchExchange,
72
+ HttpMethod,
71
73
  RequestInterceptor,
72
74
  URL_RESOLVE_INTERCEPTOR_ORDER,
73
75
  } from '@ahoo-wang/fetcher';
@@ -75,8 +77,7 @@ import '@ahoo-wang/fetcher-eventstream';
75
77
  import {
76
78
  CommandClient,
77
79
  CommandRequest,
78
- HttpMethod,
79
- CommandHttpHeaders,
80
+ CommandHeaders,
80
81
  CommandStage,
81
82
  } from '@ahoo-wang/fetcher-wow';
82
83
  import { idGenerator } from '@ahoo-wang/fetcher-cosec';
@@ -126,7 +127,7 @@ type AddCartItemCommand = CommandRequest<AddCartItem>;
126
127
  const addCartItemCommand: AddCartItemCommand = {
127
128
  method: HttpMethod.POST,
128
129
  headers: {
129
- [CommandHttpHeaders.WAIT_STAGE]: CommandStage.SNAPSHOT,
130
+ [CommandHeaders.WAIT_STAGE]: CommandStage.SNAPSHOT,
130
131
  },
131
132
  body: {
132
133
  productId: 'productId',
@@ -159,9 +160,44 @@ for await (const commandResultEvent of commandResultStream) {
159
160
 
160
161
  ### Query Module
161
162
 
162
- #### Condition Builder
163
+ #### Filter Expression Builder
163
164
 
164
- Comprehensive query condition builder with operator support:
165
+ Wow 8.11+ queries use `FilterExpression`:
166
+
167
+ ```typescript
168
+ import {
169
+ DeletionState,
170
+ filter,
171
+ SearchMode,
172
+ TimeUnit,
173
+ } from '@ahoo-wang/fetcher-wow';
174
+
175
+ const expression = filter.and([
176
+ filter.deletion(DeletionState.ACTIVE),
177
+ filter.eq('state.status', 'PAID'),
178
+ filter.elementMatch('state.items', filter.gt('quantity', 0)),
179
+ filter.search('event sourcing', {
180
+ mode: SearchMode.PHRASE,
181
+ fields: ['state.title', 'state.description'],
182
+ }),
183
+ filter.yesterday('state.createdAt', {
184
+ zoneId: 'Asia/Shanghai',
185
+ timeUnit: TimeUnit.MILLISECONDS,
186
+ }),
187
+ ]);
188
+ ```
189
+
190
+ Builders are grouped under `filter`: `matchAll`, `matchNone`, `and`, `or`,
191
+ `nor`, comparisons, string/collection predicates, presence checks,
192
+ `elementMatch`, `search`, deletion scope, and relative-time filters.
193
+ `and`, `or`, `nor`, `ids`, `aggregateIds`, `isIn`, `notIn`, and
194
+ `containsAll` accept one non-empty `readonly` array and throw `TypeError` for
195
+ an empty array.
196
+
197
+ #### Condition Builder (Deprecated)
198
+
199
+ The legacy Condition API remains available for compatibility with older Wow
200
+ servers. New code should use `FilterExpression` and `filter.*`.
165
201
 
166
202
  ```typescript
167
203
  import {
@@ -246,7 +282,7 @@ const arrayConditions = [
246
282
  // Date conditions
247
283
  const dateConditions = [
248
284
  today('createdAt'),
249
- beforeToday('lastLogin', 7), // 7 days before today (i.e., within last 7 days)
285
+ beforeToday('lastLogin', '09:30'),
250
286
  tomorrow('scheduledDate'),
251
287
  thisWeek('updatedAt'),
252
288
  nextWeek('startDate'),
@@ -282,7 +318,7 @@ const rawCondition = raw({ $text: { $search: 'keywords' } });
282
318
  | String | `contains`, `startsWith`, `endsWith`, `match` |
283
319
  | Collection | `isIn`, `notIn`, `allIn`, `elemMatch` |
284
320
  | Null/Boolean | `isNull`, `notNull`, `isTrue`, `isFalse`, `exists` |
285
- | Date | `today`, `beforeToday(days)`, `tomorrow`, `thisWeek`, `nextWeek`, `lastWeek`, `thisMonth`, `lastMonth`, `recentDays(days)`, `earlierDays(days)` |
321
+ | Date | `today`, `beforeToday(time)`, `tomorrow`, `thisWeek`, `nextWeek`, `lastWeek`, `thisMonth`, `lastMonth`, `recentDays(days)`, `earlierDays(days)` |
286
322
  | ID | `id`, `ids`, `aggregateId`, `aggregateIds`, `tenantId`, `ownerId` |
287
323
  | State | `active`, `all`, `deleted` |
288
324
  | Special | `raw` (for advanced database-specific queries) |
@@ -300,20 +336,25 @@ import {
300
336
  } from '@ahoo-wang/fetcher';
301
337
  import '@ahoo-wang/fetcher-eventstream';
302
338
  import {
339
+ aggregation,
303
340
  SnapshotQueryClient,
304
- all,
305
- ListQuery,
306
- PagedQuery,
307
- SingleQuery,
341
+ filter,
342
+ FilterListQuery,
343
+ FilterPagedQuery,
344
+ FilterSingleQuery,
345
+ type AggregationQuery,
346
+ Identifier,
308
347
  } from '@ahoo-wang/fetcher-wow';
309
348
  import { idGenerator } from '@ahoo-wang/fetcher-cosec';
310
349
 
311
350
  interface CartItem {
312
351
  productId: string;
352
+ price: number;
313
353
  quantity: number;
314
354
  }
315
355
 
316
356
  interface CartState extends Identifier {
357
+ status: string;
317
358
  items: CartItem[];
318
359
  }
319
360
 
@@ -346,11 +387,11 @@ const cartSnapshotQueryClient = new SnapshotQueryClient<CartState>({
346
387
  });
347
388
 
348
389
  // Count snapshots
349
- const count = await cartSnapshotQueryClient.count(all());
390
+ const count = await cartSnapshotQueryClient.count(filter.matchAll());
350
391
 
351
392
  // List snapshots
352
- const listQuery: ListQuery = {
353
- condition: all(),
393
+ const listQuery: FilterListQuery = {
394
+ filter: filter.matchAll(),
354
395
  };
355
396
  const list = await cartSnapshotQueryClient.list(listQuery);
356
397
 
@@ -372,8 +413,8 @@ for await (const event of stateStream) {
372
413
  }
373
414
 
374
415
  // Paged snapshots
375
- const pagedQuery: PagedQuery = {
376
- condition: all(),
416
+ const pagedQuery: FilterPagedQuery = {
417
+ filter: filter.matchAll(),
377
418
  };
378
419
  const paged = await cartSnapshotQueryClient.paged(pagedQuery);
379
420
 
@@ -381,31 +422,77 @@ const paged = await cartSnapshotQueryClient.paged(pagedQuery);
381
422
  const pagedState = await cartSnapshotQueryClient.pagedState(pagedQuery);
382
423
 
383
424
  // Single snapshot
384
- const singleQuery: SingleQuery = {
385
- condition: all(),
425
+ const singleQuery: FilterSingleQuery = {
426
+ filter: filter.matchAll(),
386
427
  };
387
428
  const single = await cartSnapshotQueryClient.single(singleQuery);
388
429
 
389
430
  // Single snapshot state
390
431
  const singleState = await cartSnapshotQueryClient.singleState(singleQuery);
432
+
433
+ type CartFields = 'state.status' | 'state.items';
434
+ type ItemFields = 'productId' | 'price' | 'quantity';
435
+
436
+ type ProductSummary = {
437
+ product: string;
438
+ representativeProduct: string | null;
439
+ itemCount: number;
440
+ revenue: number;
441
+ };
442
+
443
+ const revenue = aggregation.multiply(
444
+ aggregation.field<ItemFields>('price'),
445
+ aggregation.field<ItemFields>('quantity'),
446
+ );
447
+
448
+ const aggregationQuery: AggregationQuery<CartFields, ItemFields> = {
449
+ filter: filter.eq('state.status', 'COMPLETED'),
450
+ elements: [aggregation.element('state.items', filter.gt('quantity', 0))],
451
+ groupBy: [aggregation.terms('productId', 'product')],
452
+ metrics: [
453
+ aggregation.any('productId', 'representativeProduct'),
454
+ aggregation.count('itemCount'),
455
+ aggregation.sum(revenue, 'revenue'),
456
+ ],
457
+ };
458
+
459
+ const summaries =
460
+ await cartSnapshotQueryClient.aggregate<ProductSummary>(aggregationQuery);
461
+ const summaryStream =
462
+ await cartSnapshotQueryClient.aggregateStream<ProductSummary>(
463
+ aggregationQuery,
464
+ );
465
+ for await (const event of summaryStream) {
466
+ console.log(event.data);
467
+ }
391
468
  ```
392
469
 
470
+ `aggregation.any(field, alias)` adds a metric, not another group. It returns one
471
+ non-null scalar from the current group, or `null` when no value exists. The
472
+ selected value is intentionally unspecified and may differ across backends or
473
+ executions; use it only when every candidate is interchangeable. Its field is
474
+ relative to the innermost element, and Wow rejects collection or non-terms-capable
475
+ fields at runtime. Sorting by an `ANY` alias is an expensive metric sort.
476
+
393
477
  ##### Methods
394
478
 
395
- - `count(condition: Condition): Promise<number>` - Counts the number of snapshots that match the given condition.
396
- - `list(listQuery: ListQuery): Promise<Partial<MaterializedSnapshot<S>>[]>` - Retrieves a list of materialized
479
+ - `count(filter: FilterExpression): Promise<number>` - Counts snapshots matching the filter expression.
480
+ - `list(listQuery: FilterListQuery): Promise<Partial<MaterializedSnapshot<S>>[]>` - Retrieves a list of materialized
397
481
  snapshots.
398
- - `listStream(listQuery: ListQuery): Promise<ReadableStream<JsonServerSentEvent<Partial<MaterializedSnapshot<S>>>>>` -
482
+ - `listStream(listQuery: FilterListQuery): Promise<ReadableStream<JsonServerSentEvent<Partial<MaterializedSnapshot<S>>>>>` -
399
483
  Retrieves a stream of materialized snapshots as Server-Sent Events.
400
- - `listState(listQuery: ListQuery): Promise<Partial<S>[]>` - Retrieves a list of snapshot states.
401
- - `listStateStream(listQuery: ListQuery): Promise<ReadableStream<JsonServerSentEvent<Partial<S>>>>` - Retrieves a stream
484
+ - `listState(listQuery: FilterListQuery): Promise<Partial<S>[]>` - Retrieves a list of snapshot states.
485
+ - `listStateStream(listQuery: FilterListQuery): Promise<ReadableStream<JsonServerSentEvent<Partial<S>>>>` - Retrieves a stream
402
486
  of snapshot states as Server-Sent Events.
403
- - `paged(pagedQuery: PagedQuery): Promise<PagedList<Partial<MaterializedSnapshot<S>>>>` - Retrieves a paged list of
487
+ - `paged(pagedQuery: FilterPagedQuery): Promise<PagedList<Partial<MaterializedSnapshot<S>>>>` - Retrieves a paged list of
404
488
  materialized snapshots.
405
- - `pagedState(pagedQuery: PagedQuery): Promise<PagedList<Partial<S>>>` - Retrieves a paged list of snapshot states.
406
- - `single(singleQuery: SingleQuery): Promise<Partial<MaterializedSnapshot<S>>>` - Retrieves a single materialized
489
+ - `pagedState(pagedQuery: FilterPagedQuery): Promise<PagedList<Partial<S>>>` - Retrieves a paged list of snapshot states.
490
+ - `single(singleQuery: FilterSingleQuery): Promise<Partial<MaterializedSnapshot<S>>>` - Retrieves a single materialized
407
491
  snapshot.
408
- - `singleState(singleQuery: SingleQuery): Promise<Partial<S>>` - Retrieves a single snapshot state.
492
+ - `singleState(singleQuery: FilterSingleQuery): Promise<Partial<S>>` - Retrieves a single snapshot state.
493
+ - `aggregate<Row extends DynamicDocument = DynamicDocument, AGGREGATION_FIELDS extends string = string>(query: AggregationQuery<FIELDS, AGGREGATION_FIELDS>): Promise<Row[]>` - Runs a
494
+ snapshot aggregation and requests `snapshot/aggregation`.
495
+ - `aggregateStream<Row extends DynamicDocument = DynamicDocument, AGGREGATION_FIELDS extends string = string>(query: AggregationQuery<FIELDS, AGGREGATION_FIELDS>): Promise<ReadableStream<JsonServerSentEvent<Row>>>` - Runs a snapshot aggregation and requests `snapshot/aggregation` using Server-Sent Events (SSE).
409
496
 
410
497
  #### QueryClientFactory
411
498
 
@@ -413,9 +500,9 @@ Factory for creating pre-configured query clients. Useful when you need multiple
413
500
 
414
501
  ```typescript
415
502
  import {
503
+ filter,
416
504
  QueryClientFactory,
417
505
  ResourceAttributionPathSpec,
418
- all,
419
506
  } from '@ahoo-wang/fetcher-wow';
420
507
  import { idGenerator } from '@ahoo-wang/fetcher-cosec';
421
508
 
@@ -431,7 +518,7 @@ const factory = new QueryClientFactory({
431
518
  const snapshotClient = factory.createSnapshotQueryClient({
432
519
  aggregateName: 'cart',
433
520
  });
434
- const carts = await snapshotClient.listState({ condition: all() });
521
+ const carts = await snapshotClient.listState({ filter: filter.matchAll() });
435
522
 
436
523
  // Create a state aggregate client
437
524
  const stateClient = factory.createLoadStateAggregateClient({
@@ -443,7 +530,7 @@ const cart = await stateClient.load('cart-123');
443
530
  const eventClient = factory.createEventStreamQueryClient({
444
531
  aggregateName: 'cart',
445
532
  });
446
- const events = await eventClient.list({ condition: all() });
533
+ const events = await eventClient.list({ filter: filter.matchAll() });
447
534
  ```
448
535
 
449
536
  **Methods:**
@@ -467,9 +554,9 @@ import {
467
554
  import '@ahoo-wang/fetcher-eventstream';
468
555
  import {
469
556
  EventStreamQueryClient,
470
- all,
471
- ListQuery,
472
- PagedQuery,
557
+ filter,
558
+ FilterListQuery,
559
+ FilterPagedQuery,
473
560
  } from '@ahoo-wang/fetcher-wow';
474
561
  import { idGenerator } from '@ahoo-wang/fetcher-cosec';
475
562
 
@@ -502,11 +589,11 @@ const cartEventStreamQueryClient = new EventStreamQueryClient({
502
589
  });
503
590
 
504
591
  // Count event streams
505
- const count = await cartEventStreamQueryClient.count(all());
592
+ const count = await cartEventStreamQueryClient.count(filter.matchAll());
506
593
 
507
594
  // List event streams
508
- const listQuery: ListQuery = {
509
- condition: all(),
595
+ const listQuery: FilterListQuery = {
596
+ filter: filter.matchAll(),
510
597
  };
511
598
  const list = await cartEventStreamQueryClient.list(listQuery);
512
599
 
@@ -518,20 +605,19 @@ for await (const event of listStream) {
518
605
  }
519
606
 
520
607
  // Paged event streams
521
- const pagedQuery: PagedQuery = {
522
- condition: all(),
608
+ const pagedQuery: FilterPagedQuery = {
609
+ filter: filter.matchAll(),
523
610
  };
524
611
  const paged = await cartEventStreamQueryClient.paged(pagedQuery);
525
612
  ```
526
613
 
527
614
  ##### Methods
528
615
 
529
- - `count(condition: Condition): Promise<number>` - Counts the number of domain event streams that match the given
530
- condition.
531
- - `list(listQuery: ListQuery): Promise<Partial<DomainEventStream>[]>` - Retrieves a list of domain event streams.
532
- - `listStream(listQuery: ListQuery): Promise<ReadableStream<JsonServerSentEvent<Partial<DomainEventStream>>>>` -
616
+ - `count(filter: FilterExpression): Promise<number>` - Counts domain event streams matching the filter expression.
617
+ - `list(listQuery: FilterListQuery): Promise<Partial<DomainEventStream>[]>` - Retrieves a list of domain event streams.
618
+ - `listStream(listQuery: FilterListQuery): Promise<ReadableStream<JsonServerSentEvent<Partial<DomainEventStream>>>>` -
533
619
  Retrieves a stream of domain event streams as Server-Sent Events.
534
- - `paged(pagedQuery: PagedQuery): Promise<PagedList<Partial<DomainEventStream>>>` - Retrieves a paged list of domain
620
+ - `paged(pagedQuery: FilterPagedQuery): Promise<PagedList<Partial<DomainEventStream>>>` - Retrieves a paged list of domain
535
621
  event streams.
536
622
 
537
623
  ## 🚀 Advanced Usage Examples
@@ -673,8 +759,9 @@ Create complex queries with reactive real-time updates:
673
759
 
674
760
  ```typescript
675
761
  import {
676
- SnapshotQueryClient,
677
762
  EventStreamQueryClient,
763
+ filter,
764
+ SnapshotQueryClient,
678
765
  } from '@ahoo-wang/fetcher-wow';
679
766
 
680
767
  // Advanced query manager with reactive updates
@@ -725,17 +812,16 @@ class ReactiveQueryManager {
725
812
  async getUserDashboardStats(userId: string) {
726
813
  const [userProfile, recentActivity, stats] = await Promise.all([
727
814
  this.snapshotClient.single({
728
- condition: { id: userId },
729
- projection: { name: 1, email: 1, createdAt: 1 },
815
+ filter: filter.eq('aggregateId', userId),
730
816
  }),
731
817
  this.snapshotClient.list({
732
- condition: { userId, type: 'activity' },
733
- sort: [{ field: 'timestamp', order: 'desc' }],
818
+ filter: filter.and([
819
+ filter.eq('state.userId', userId),
820
+ filter.eq('state.type', 'activity'),
821
+ ]),
734
822
  limit: 10,
735
823
  }),
736
- this.snapshotClient.count({
737
- condition: { userId },
738
- }),
824
+ this.snapshotClient.count(filter.eq('state.userId', userId)),
739
825
  ]);
740
826
 
741
827
  return {
@@ -758,8 +844,10 @@ console.log('Dashboard:', dashboard);
758
844
  queryManager.subscribeToQuery(
759
845
  'user-activity',
760
846
  {
761
- condition: { userId: 'user-123', type: 'activity' },
762
- sort: [{ field: 'timestamp', order: 'desc' }],
847
+ filter: filter.and([
848
+ filter.eq('state.userId', 'user-123'),
849
+ filter.eq('state.type', 'activity'),
850
+ ]),
763
851
  },
764
852
  update => {
765
853
  console.log('New activity:', update);
@@ -774,6 +862,7 @@ queryManager.subscribeToQuery(
774
862
  import {
775
863
  Fetcher,
776
864
  FetchExchange,
865
+ HttpMethod,
777
866
  RequestInterceptor,
778
867
  URL_RESOLVE_INTERCEPTOR_ORDER,
779
868
  } from '@ahoo-wang/fetcher';
@@ -781,12 +870,11 @@ import '@ahoo-wang/fetcher-eventstream';
781
870
  import {
782
871
  CommandClient,
783
872
  CommandRequest,
784
- CommandHttpHeaders,
873
+ CommandHeaders,
785
874
  CommandStage,
786
- HttpMethod,
787
875
  SnapshotQueryClient,
788
- all,
789
- ListQuery,
876
+ filter,
877
+ FilterListQuery,
790
878
  } from '@ahoo-wang/fetcher-wow';
791
879
  import { idGenerator } from '@ahoo-wang/fetcher-cosec';
792
880
 
@@ -850,7 +938,7 @@ type AddCartItemCommand = CommandRequest<AddCartItem>;
850
938
  const addItemCommand: AddCartItemCommand = {
851
939
  method: HttpMethod.POST,
852
940
  headers: {
853
- [CommandHttpHeaders.WAIT_STAGE]: CommandStage.SNAPSHOT,
941
+ [CommandHeaders.WAIT_STAGE]: CommandStage.SNAPSHOT,
854
942
  },
855
943
  body: {
856
944
  productId: 'product-123',
@@ -865,8 +953,8 @@ const commandResult = await cartCommandClient.send(
865
953
  console.log('Command executed:', commandResult);
866
954
 
867
955
  // 2. Query the updated cart
868
- const listQuery: ListQuery = {
869
- condition: all(),
956
+ const listQuery: FilterListQuery = {
957
+ filter: filter.matchAll(),
870
958
  };
871
959
  const carts = await cartSnapshotQueryClient.list(listQuery);
872
960
 
@@ -895,7 +983,7 @@ pnpm test --coverage
895
983
  ## 🤝 Contributing
896
984
 
897
985
  Contributions are welcome! Please see
898
- the [contributing guide](https://github.com/Ahoo-Wang/fetcher/blob/main/CONTRIBUTING.md) for more details.
986
+ the [contributing guide](https://github.com/Ahoo-Wang/fetcher/blob/main/wiki/guide/contributing.md) for more details.
899
987
 
900
988
  ## 📄 License
901
989