@ahoo-wang/fetcher-wow 2.8.8 → 2.9.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 +239 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,11 +25,11 @@ working with the Wow CQRS/DDD framework.
|
|
|
25
25
|
- **🔍 Powerful Query DSL**: Rich query condition builder with comprehensive operator support for complex querying
|
|
26
26
|
- **🔍 Query Clients**: Specialized clients for querying snapshot and event stream data with comprehensive query
|
|
27
27
|
operations:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
- Counting resources
|
|
29
|
+
- Listing resources
|
|
30
|
+
- Streaming resources as Server-Sent Events
|
|
31
|
+
- Paging resources
|
|
32
|
+
- Retrieving single resources
|
|
33
33
|
|
|
34
34
|
## 🚀 Quick Start
|
|
35
35
|
|
|
@@ -407,9 +407,241 @@ const paged = await cartEventStreamQueryClient.paged(pagedQuery);
|
|
|
407
407
|
- `paged(pagedQuery: PagedQuery): Promise<PagedList<Partial<DomainEventStream>>>` - Retrieves a paged list of domain
|
|
408
408
|
event streams.
|
|
409
409
|
|
|
410
|
-
##
|
|
410
|
+
## 🚀 Advanced Usage Examples
|
|
411
|
+
|
|
412
|
+
### Custom Command Builders and Validators
|
|
413
|
+
|
|
414
|
+
Create type-safe command builders with validation:
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
import { CommandClient, CommandRequest } from '@ahoo-wang/fetcher-wow';
|
|
418
|
+
|
|
419
|
+
// Command type definitions
|
|
420
|
+
interface CreateUserCommand {
|
|
421
|
+
commandType: 'CreateUser';
|
|
422
|
+
commandId: string;
|
|
423
|
+
aggregateId: string;
|
|
424
|
+
name: string;
|
|
425
|
+
email: string;
|
|
426
|
+
role: 'admin' | 'user' | 'moderator';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
interface UpdateUserProfileCommand {
|
|
430
|
+
commandType: 'UpdateUserProfile';
|
|
431
|
+
commandId: string;
|
|
432
|
+
aggregateId: string;
|
|
433
|
+
displayName?: string;
|
|
434
|
+
bio?: string;
|
|
435
|
+
avatarUrl?: string;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Command builder with validation
|
|
439
|
+
class UserCommandBuilder {
|
|
440
|
+
private commandClient: CommandClient;
|
|
441
|
+
|
|
442
|
+
constructor(commandClient: CommandClient) {
|
|
443
|
+
this.commandClient = commandClient;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async createUser(params: {
|
|
447
|
+
name: string;
|
|
448
|
+
email: string;
|
|
449
|
+
role: 'admin' | 'user' | 'moderator';
|
|
450
|
+
ownerId: string;
|
|
451
|
+
}): Promise<any> {
|
|
452
|
+
// Validate input
|
|
453
|
+
this.validateCreateUserParams(params);
|
|
454
|
+
|
|
455
|
+
const command: CreateUserCommand = {
|
|
456
|
+
commandType: 'CreateUser',
|
|
457
|
+
commandId: crypto.randomUUID(),
|
|
458
|
+
aggregateId: crypto.randomUUID(), // New user gets new aggregate ID
|
|
459
|
+
...params,
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
return this.commandClient.send(command, { ownerId: params.ownerId });
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async updateProfile(params: {
|
|
466
|
+
userId: string;
|
|
467
|
+
displayName?: string;
|
|
468
|
+
bio?: string;
|
|
469
|
+
avatarUrl?: string;
|
|
470
|
+
ownerId: string;
|
|
471
|
+
}): Promise<any> {
|
|
472
|
+
// Validate input
|
|
473
|
+
this.validateUpdateProfileParams(params);
|
|
474
|
+
|
|
475
|
+
const command: UpdateUserProfileCommand = {
|
|
476
|
+
commandType: 'UpdateUserProfile',
|
|
477
|
+
commandId: crypto.randomUUID(),
|
|
478
|
+
aggregateId: params.userId, // User ID is the aggregate ID
|
|
479
|
+
displayName: params.displayName,
|
|
480
|
+
bio: params.bio,
|
|
481
|
+
avatarUrl: params.avatarUrl,
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
return this.commandClient.send(command, { ownerId: params.ownerId });
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private validateCreateUserParams(params: any) {
|
|
488
|
+
if (!params.name || params.name.length < 2) {
|
|
489
|
+
throw new Error('Name must be at least 2 characters');
|
|
490
|
+
}
|
|
491
|
+
if (!params.email || !this.isValidEmail(params.email)) {
|
|
492
|
+
throw new Error('Valid email is required');
|
|
493
|
+
}
|
|
494
|
+
if (!['admin', 'user', 'moderator'].includes(params.role)) {
|
|
495
|
+
throw new Error('Invalid role');
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private validateUpdateProfileParams(params: any) {
|
|
500
|
+
if (!params.userId) {
|
|
501
|
+
throw new Error('User ID is required');
|
|
502
|
+
}
|
|
503
|
+
if (params.displayName && params.displayName.length > 50) {
|
|
504
|
+
throw new Error('Display name too long');
|
|
505
|
+
}
|
|
506
|
+
if (params.bio && params.bio.length > 500) {
|
|
507
|
+
throw new Error('Bio too long');
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
private isValidEmail(email: string): boolean {
|
|
512
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
513
|
+
return emailRegex.test(email);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Usage
|
|
518
|
+
const commandClient = new CommandClient({ basePath: '/api/commands' });
|
|
519
|
+
const userCommands = new UserCommandBuilder(commandClient);
|
|
520
|
+
|
|
521
|
+
try {
|
|
522
|
+
// Create a new user
|
|
523
|
+
const result = await userCommands.createUser({
|
|
524
|
+
name: 'John Doe',
|
|
525
|
+
email: 'john@example.com',
|
|
526
|
+
role: 'user',
|
|
527
|
+
ownerId: 'user-123',
|
|
528
|
+
});
|
|
529
|
+
console.log('User created:', result);
|
|
530
|
+
|
|
531
|
+
// Update user profile
|
|
532
|
+
await userCommands.updateProfile({
|
|
533
|
+
userId: result.aggregateId,
|
|
534
|
+
displayName: 'Johnny',
|
|
535
|
+
bio: 'Software developer',
|
|
536
|
+
ownerId: 'user-123',
|
|
537
|
+
});
|
|
538
|
+
} catch (error) {
|
|
539
|
+
console.error('Command failed:', error);
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Advanced Query Composition with Reactive Updates
|
|
544
|
+
|
|
545
|
+
Create complex queries with reactive real-time updates:
|
|
411
546
|
|
|
412
|
-
|
|
547
|
+
```typescript
|
|
548
|
+
import {
|
|
549
|
+
SnapshotQueryClient,
|
|
550
|
+
EventStreamQueryClient,
|
|
551
|
+
} from '@ahoo-wang/fetcher-wow';
|
|
552
|
+
|
|
553
|
+
// Advanced query manager with reactive updates
|
|
554
|
+
class ReactiveQueryManager {
|
|
555
|
+
private snapshotClient: SnapshotQueryClient;
|
|
556
|
+
private streamClient: EventStreamQueryClient;
|
|
557
|
+
private listeners: Map<string, (data: any) => void> = new Map();
|
|
558
|
+
|
|
559
|
+
constructor(basePath: string) {
|
|
560
|
+
this.snapshotClient = new SnapshotQueryClient({ basePath });
|
|
561
|
+
this.streamClient = new EventStreamQueryClient({ basePath });
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Subscribe to real-time updates for a query
|
|
565
|
+
subscribeToQuery(
|
|
566
|
+
queryId: string,
|
|
567
|
+
initialQuery: any,
|
|
568
|
+
callback: (data: any) => void,
|
|
569
|
+
) {
|
|
570
|
+
this.listeners.set(queryId, callback);
|
|
571
|
+
|
|
572
|
+
// Start streaming updates
|
|
573
|
+
this.startStreaming(queryId, initialQuery);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Unsubscribe from updates
|
|
577
|
+
unsubscribe(queryId: string) {
|
|
578
|
+
this.listeners.delete(queryId);
|
|
579
|
+
// Close stream if needed
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
private async startStreaming(queryId: string, query: any) {
|
|
583
|
+
try {
|
|
584
|
+
const stream = await this.streamClient.listStream(query);
|
|
585
|
+
|
|
586
|
+
for await (const event of stream) {
|
|
587
|
+
const listener = this.listeners.get(queryId);
|
|
588
|
+
if (listener) {
|
|
589
|
+
listener(event);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
} catch (error) {
|
|
593
|
+
console.error(`Stream error for ${queryId}:`, error);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Complex query with aggregations
|
|
598
|
+
async getUserDashboardStats(userId: string) {
|
|
599
|
+
const [userProfile, recentActivity, stats] = await Promise.all([
|
|
600
|
+
this.snapshotClient.single({
|
|
601
|
+
condition: { id: userId },
|
|
602
|
+
projection: { name: 1, email: 1, createdAt: 1 },
|
|
603
|
+
}),
|
|
604
|
+
this.snapshotClient.list({
|
|
605
|
+
condition: { userId, type: 'activity' },
|
|
606
|
+
sort: [{ field: 'timestamp', order: 'desc' }],
|
|
607
|
+
limit: 10,
|
|
608
|
+
}),
|
|
609
|
+
this.snapshotClient.count({
|
|
610
|
+
condition: { userId },
|
|
611
|
+
}),
|
|
612
|
+
]);
|
|
613
|
+
|
|
614
|
+
return {
|
|
615
|
+
profile: userProfile,
|
|
616
|
+
recentActivity,
|
|
617
|
+
totalActions: stats,
|
|
618
|
+
lastActivity: recentActivity[0]?.timestamp,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Usage
|
|
624
|
+
const queryManager = new ReactiveQueryManager('/api/queries');
|
|
625
|
+
|
|
626
|
+
// Get dashboard data
|
|
627
|
+
const dashboard = await queryManager.getUserDashboardStats('user-123');
|
|
628
|
+
console.log('Dashboard:', dashboard);
|
|
629
|
+
|
|
630
|
+
// Subscribe to real-time updates
|
|
631
|
+
queryManager.subscribeToQuery(
|
|
632
|
+
'user-activity',
|
|
633
|
+
{
|
|
634
|
+
condition: { userId: 'user-123', type: 'activity' },
|
|
635
|
+
sort: [{ field: 'timestamp', order: 'desc' }],
|
|
636
|
+
},
|
|
637
|
+
update => {
|
|
638
|
+
console.log('New activity:', update);
|
|
639
|
+
// Update UI with new data
|
|
640
|
+
},
|
|
641
|
+
);
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
## 🛠️ Advanced Usage
|
|
413
645
|
|
|
414
646
|
```typescript
|
|
415
647
|
import {
|