@ahoo-wang/fetcher-wow 0.9.6 โ 0.9.9
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 +541 -0
- package/README.zh-CN.md +540 -0
- package/dist/command/commandRequest.d.ts +31 -0
- package/dist/command/commandRequest.d.ts.map +1 -0
- package/dist/command/commandResult.d.ts +12 -0
- package/dist/command/commandResult.d.ts.map +1 -0
- package/dist/command/commandResultEventStream.d.ts +14 -0
- package/dist/command/commandResultEventStream.d.ts.map +1 -0
- package/dist/command/index.d.ts +3 -1
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/types.d.ts +4 -2
- package/dist/command/types.d.ts.map +1 -1
- package/dist/index.es.js +176 -161
- package/dist/index.umd.js +1 -1
- package/dist/query/index.d.ts +0 -1
- package/dist/query/index.d.ts.map +1 -1
- package/package.json +3 -4
- package/dist/command/commandGateway.d.ts +0 -3
- package/dist/command/commandGateway.d.ts.map +0 -1
- package/dist/query/snapshotQueryService.d.ts +0 -3
- package/dist/query/snapshotQueryService.d.ts.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
# @ahoo-wang/fetcher-wow
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@ahoo-wang/fetcher-wow)
|
|
4
|
+
[](https://github.com/Ahoo-Wang/fetcher/actions)
|
|
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
|
+
|
|
11
|
+
Support for [Wow](https://github.com/Ahoo-Wang/Wow) in Fetcher. Provides TypeScript types and utilities for working with
|
|
12
|
+
the Wow CQRS/DDD framework.
|
|
13
|
+
|
|
14
|
+
## ๐ Features
|
|
15
|
+
|
|
16
|
+
- **๐ฆ Comprehensive Type Definitions**: Full TypeScript support for Wow framework entities
|
|
17
|
+
- **๐ง Command Utilities**: Helpers for working with Wow commands and command results
|
|
18
|
+
- **๐ Query DSL**: Rich query condition builders with operator support
|
|
19
|
+
- **๐ก Event Stream Support**: Integration with Server-Sent Events for real-time command results
|
|
20
|
+
- **๐ CQRS Pattern**: Support for Command Query Responsibility Segregation patterns
|
|
21
|
+
- **๐งฑ DDD Building Blocks**: Domain-driven design types for aggregates, events, and more
|
|
22
|
+
|
|
23
|
+
## ๐ Quick Start
|
|
24
|
+
|
|
25
|
+
### Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Using npm
|
|
29
|
+
npm install @ahoo-wang/fetcher-wow
|
|
30
|
+
|
|
31
|
+
# Using pnpm
|
|
32
|
+
pnpm add @ahoo-wang/fetcher-wow
|
|
33
|
+
|
|
34
|
+
# Using yarn
|
|
35
|
+
yarn add @ahoo-wang/fetcher-wow
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## ๐ API Reference
|
|
39
|
+
|
|
40
|
+
### Command Module
|
|
41
|
+
|
|
42
|
+
#### CommandHeaders
|
|
43
|
+
|
|
44
|
+
Constants for standard HTTP headers used in Wow command processing:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { CommandHeaders } from '@ahoo-wang/fetcher-wow';
|
|
48
|
+
|
|
49
|
+
// Example usage
|
|
50
|
+
const request = {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: {
|
|
53
|
+
[CommandHeaders.TENANT_ID]: 'tenant-123',
|
|
54
|
+
[CommandHeaders.AGGREGATE_ID]: 'aggregate-456',
|
|
55
|
+
[CommandHeaders.REQUEST_ID]: 'request-789',
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify(command),
|
|
58
|
+
};
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Key headers include:
|
|
62
|
+
|
|
63
|
+
- `TENANT_ID` - Tenant identifier
|
|
64
|
+
- `OWNER_ID` - Owner identifier
|
|
65
|
+
- `AGGREGATE_ID` - Aggregate root identifier
|
|
66
|
+
- `AGGREGATE_VERSION` - Expected aggregate version
|
|
67
|
+
- `REQUEST_ID` - Request tracking ID
|
|
68
|
+
- `WAIT_*` - Various wait condition headers
|
|
69
|
+
- `LOCAL_FIRST` - Local processing preference
|
|
70
|
+
- And many more...
|
|
71
|
+
|
|
72
|
+
#### CommandRequest
|
|
73
|
+
|
|
74
|
+
Interface for command requests with full configuration options:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { CommandRequest, CommandHeaders } from '@ahoo-wang/fetcher-wow';
|
|
78
|
+
|
|
79
|
+
const commandRequest: CommandRequest = {
|
|
80
|
+
path: '/commands/CreateUser',
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: {
|
|
83
|
+
[CommandHeaders.TENANT_ID]: 'tenant-123',
|
|
84
|
+
},
|
|
85
|
+
body: {
|
|
86
|
+
name: 'John Doe',
|
|
87
|
+
email: 'john@example.com',
|
|
88
|
+
},
|
|
89
|
+
timeout: 5000,
|
|
90
|
+
aggregateId: 'user-456',
|
|
91
|
+
requestId: 'req-789',
|
|
92
|
+
localFirst: true,
|
|
93
|
+
stream: false,
|
|
94
|
+
};
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### CommandResult
|
|
98
|
+
|
|
99
|
+
Interface representing the result of command execution:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { CommandResult, CommandStage } from '@ahoo-wang/fetcher-wow';
|
|
103
|
+
|
|
104
|
+
const commandResult: CommandResult = {
|
|
105
|
+
id: 'result-123',
|
|
106
|
+
commandId: 'cmd-456',
|
|
107
|
+
requestId: 'req-789',
|
|
108
|
+
stage: CommandStage.PROCESSED,
|
|
109
|
+
contextName: 'user-context',
|
|
110
|
+
aggregateName: 'User',
|
|
111
|
+
aggregateId: 'user-456',
|
|
112
|
+
aggregateVersion: 1,
|
|
113
|
+
errorCode: 'Ok',
|
|
114
|
+
errorMsg: '',
|
|
115
|
+
function: {
|
|
116
|
+
functionKind: 'COMMAND',
|
|
117
|
+
contextName: 'user-context',
|
|
118
|
+
processorName: 'UserProcessor',
|
|
119
|
+
name: 'CreateUser',
|
|
120
|
+
},
|
|
121
|
+
signalTime: Date.now(),
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
#### CommandResultEventStream
|
|
126
|
+
|
|
127
|
+
Utilities for working with command result event streams:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import {
|
|
131
|
+
toCommandResultEventStream,
|
|
132
|
+
CommandResultEvent,
|
|
133
|
+
} from '@ahoo-wang/fetcher-wow';
|
|
134
|
+
import { fetchEventStream } from '@ahoo-wang/fetcher-eventstream';
|
|
135
|
+
|
|
136
|
+
// Convert ServerSentEventStream to CommandResultEventStream
|
|
137
|
+
const eventStream = fetchEventStream('/commands/stream');
|
|
138
|
+
const commandResultStream = toCommandResultEventStream(eventStream);
|
|
139
|
+
|
|
140
|
+
// Process command results as they arrive
|
|
141
|
+
const reader = commandResultStream.getReader();
|
|
142
|
+
while (true) {
|
|
143
|
+
const { done, value } = await reader.read();
|
|
144
|
+
if (done) break;
|
|
145
|
+
|
|
146
|
+
const commandResult: CommandResult = value.data;
|
|
147
|
+
console.log('Command result:', commandResult);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Query Module
|
|
152
|
+
|
|
153
|
+
#### Condition Builder
|
|
154
|
+
|
|
155
|
+
Comprehensive query condition builder with operator support:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import {
|
|
159
|
+
and,
|
|
160
|
+
or,
|
|
161
|
+
eq,
|
|
162
|
+
ne,
|
|
163
|
+
gt,
|
|
164
|
+
lt,
|
|
165
|
+
contains,
|
|
166
|
+
isIn,
|
|
167
|
+
between,
|
|
168
|
+
today,
|
|
169
|
+
active,
|
|
170
|
+
} from '@ahoo-wang/fetcher-wow';
|
|
171
|
+
|
|
172
|
+
// Simple conditions
|
|
173
|
+
const simpleConditions = [
|
|
174
|
+
eq('name', 'John'),
|
|
175
|
+
ne('status', 'inactive'),
|
|
176
|
+
gt('age', 18),
|
|
177
|
+
lt('score', 100),
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
// Complex conditions
|
|
181
|
+
const complexCondition = and(
|
|
182
|
+
eq('tenantId', 'tenant-123'),
|
|
183
|
+
or(
|
|
184
|
+
contains('email', '@company.com'),
|
|
185
|
+
isIn('department', 'engineering', 'marketing'),
|
|
186
|
+
),
|
|
187
|
+
between('salary', 50000, 100000),
|
|
188
|
+
today('createdAt'),
|
|
189
|
+
active(),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Date conditions
|
|
193
|
+
const dateConditions = [
|
|
194
|
+
today('createdAt'),
|
|
195
|
+
beforeToday('lastLogin', 7), // Within last 7 days
|
|
196
|
+
thisWeek('updatedAt'),
|
|
197
|
+
lastMonth('createdDate'),
|
|
198
|
+
];
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### Operators
|
|
202
|
+
|
|
203
|
+
Full operator enumeration for query building:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { Operator } from '@ahoo-wang/fetcher-wow';
|
|
207
|
+
|
|
208
|
+
// Logical operators
|
|
209
|
+
(Operator.AND, Operator.OR, Operator.NOR);
|
|
210
|
+
|
|
211
|
+
// Comparison operators
|
|
212
|
+
(Operator.EQ,
|
|
213
|
+
Operator.NE,
|
|
214
|
+
Operator.GT,
|
|
215
|
+
Operator.LT,
|
|
216
|
+
Operator.GTE,
|
|
217
|
+
Operator.LTE);
|
|
218
|
+
|
|
219
|
+
// Membership operators
|
|
220
|
+
(Operator.IN, Operator.NOT_IN, Operator.ALL_IN, Operator.BETWEEN);
|
|
221
|
+
|
|
222
|
+
// String operators
|
|
223
|
+
(Operator.CONTAINS, Operator.STARTS_WITH, Operator.ENDS_WITH);
|
|
224
|
+
|
|
225
|
+
// Existence operators
|
|
226
|
+
(Operator.NULL, Operator.NOT_NULL, Operator.EXISTS);
|
|
227
|
+
|
|
228
|
+
// Boolean operators
|
|
229
|
+
(Operator.TRUE, Operator.FALSE);
|
|
230
|
+
|
|
231
|
+
// Date operators
|
|
232
|
+
(Operator.TODAY,
|
|
233
|
+
Operator.BEFORE_TODAY,
|
|
234
|
+
Operator.TOMORROW,
|
|
235
|
+
Operator.THIS_WEEK,
|
|
236
|
+
Operator.NEXT_WEEK,
|
|
237
|
+
Operator.LAST_WEEK,
|
|
238
|
+
Operator.THIS_MONTH,
|
|
239
|
+
Operator.LAST_MONTH,
|
|
240
|
+
Operator.RECENT_DAYS,
|
|
241
|
+
Operator.EARLIER_DAYS);
|
|
242
|
+
|
|
243
|
+
// Special operators
|
|
244
|
+
(Operator.ID,
|
|
245
|
+
Operator.IDS,
|
|
246
|
+
Operator.AGGREGATE_ID,
|
|
247
|
+
Operator.AGGREGATE_IDS,
|
|
248
|
+
Operator.TENANT_ID,
|
|
249
|
+
Operator.OWNER_ID,
|
|
250
|
+
Operator.DELETED,
|
|
251
|
+
Operator.ALL,
|
|
252
|
+
Operator.ELEM_MATCH,
|
|
253
|
+
Operator.RAW);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### Queryable Interface
|
|
257
|
+
|
|
258
|
+
Interfaces for building queries with sorting, pagination, and projection:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import {
|
|
262
|
+
Queryable,
|
|
263
|
+
SortDirection,
|
|
264
|
+
DEFAULT_PAGINATION,
|
|
265
|
+
} from '@ahoo-wang/fetcher-wow';
|
|
266
|
+
|
|
267
|
+
const query: Queryable = {
|
|
268
|
+
condition: eq('status', 'active'),
|
|
269
|
+
sort: [
|
|
270
|
+
{ field: 'createdAt', direction: SortDirection.DESC },
|
|
271
|
+
{ field: 'name', direction: SortDirection.ASC },
|
|
272
|
+
],
|
|
273
|
+
projection: {
|
|
274
|
+
include: ['id', 'name', 'email', 'status'],
|
|
275
|
+
exclude: ['password', 'internalNotes'],
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const pagedQuery = {
|
|
280
|
+
...query,
|
|
281
|
+
pagination: {
|
|
282
|
+
index: 2,
|
|
283
|
+
size: 20,
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Types Module
|
|
289
|
+
|
|
290
|
+
#### Core Types
|
|
291
|
+
|
|
292
|
+
Essential types for domain modeling:
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import {
|
|
296
|
+
Identifier,
|
|
297
|
+
Version,
|
|
298
|
+
TenantId,
|
|
299
|
+
OwnerId,
|
|
300
|
+
NamedAggregate,
|
|
301
|
+
AggregateId,
|
|
302
|
+
StateCapable,
|
|
303
|
+
} from '@ahoo-wang/fetcher-wow';
|
|
304
|
+
|
|
305
|
+
interface User
|
|
306
|
+
extends Identifier,
|
|
307
|
+
Version,
|
|
308
|
+
TenantId,
|
|
309
|
+
OwnerId,
|
|
310
|
+
NamedAggregate,
|
|
311
|
+
StateCapable<UserState> {
|
|
312
|
+
id: string;
|
|
313
|
+
version: number;
|
|
314
|
+
tenantId: string;
|
|
315
|
+
ownerId: string;
|
|
316
|
+
contextName: string;
|
|
317
|
+
aggregateName: string;
|
|
318
|
+
state: UserState;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
interface UserState {
|
|
322
|
+
name: string;
|
|
323
|
+
email: string;
|
|
324
|
+
status: 'active' | 'inactive';
|
|
325
|
+
createdAt: number;
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### Error Handling
|
|
330
|
+
|
|
331
|
+
Standard error types and codes:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { ErrorInfo, ErrorCodes, RecoverableType } from '@ahoo-wang/fetcher-wow';
|
|
335
|
+
|
|
336
|
+
const errorInfo: ErrorInfo = {
|
|
337
|
+
errorCode: ErrorCodes.NOT_FOUND,
|
|
338
|
+
errorMsg: 'User not found',
|
|
339
|
+
bindingErrors: [],
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Check error types
|
|
343
|
+
if (ErrorCodes.isSucceeded(errorInfo.errorCode)) {
|
|
344
|
+
console.log('Operation successful');
|
|
345
|
+
} else {
|
|
346
|
+
console.error('Operation failed:', errorInfo.errorMsg);
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### Function Types
|
|
351
|
+
|
|
352
|
+
Function information for event and command handlers:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { FunctionInfo, FunctionKind } from '@ahoo-wang/fetcher-wow';
|
|
356
|
+
|
|
357
|
+
const functionInfo: FunctionInfo = {
|
|
358
|
+
functionKind: FunctionKind.COMMAND,
|
|
359
|
+
contextName: 'user-context',
|
|
360
|
+
processorName: 'UserProcessor',
|
|
361
|
+
name: 'CreateUser',
|
|
362
|
+
};
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## ๐ ๏ธ Advanced Usage
|
|
366
|
+
|
|
367
|
+
### Complete Command Flow Example
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import {
|
|
371
|
+
CommandRequest,
|
|
372
|
+
CommandHeaders,
|
|
373
|
+
CommandResult,
|
|
374
|
+
CommandStage,
|
|
375
|
+
toCommandResultEventStream,
|
|
376
|
+
} from '@ahoo-wang/fetcher-wow';
|
|
377
|
+
import { fetchEventStream } from '@ahoo-wang/fetcher-eventstream';
|
|
378
|
+
|
|
379
|
+
// 1. Create command request
|
|
380
|
+
const commandRequest: CommandRequest = {
|
|
381
|
+
path: '/commands/user/CreateUser',
|
|
382
|
+
method: 'POST',
|
|
383
|
+
headers: {
|
|
384
|
+
[CommandHeaders.TENANT_ID]: 'tenant-123',
|
|
385
|
+
[CommandHeaders.REQUEST_ID]: 'req-' + Date.now(),
|
|
386
|
+
},
|
|
387
|
+
body: {
|
|
388
|
+
name: 'John Doe',
|
|
389
|
+
email: 'john@example.com',
|
|
390
|
+
},
|
|
391
|
+
timeout: 10000,
|
|
392
|
+
localFirst: true,
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// 2. Execute command and wait for result
|
|
396
|
+
async function executeCommand(request: CommandRequest): Promise<CommandResult> {
|
|
397
|
+
// Implementation depends on your HTTP client
|
|
398
|
+
// This is just an example structure
|
|
399
|
+
const response = await fetch('/api' + request.path, {
|
|
400
|
+
method: request.method,
|
|
401
|
+
headers: request.headers,
|
|
402
|
+
body: JSON.stringify(request.body),
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
return response.json();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// 3. Stream command results in real-time
|
|
409
|
+
async function streamCommandResults() {
|
|
410
|
+
const eventStream = fetchEventStream('/commands/stream');
|
|
411
|
+
const commandResultStream = toCommandResultEventStream(eventStream);
|
|
412
|
+
|
|
413
|
+
const reader = commandResultStream.getReader();
|
|
414
|
+
try {
|
|
415
|
+
while (true) {
|
|
416
|
+
const { done, value } = await reader.read();
|
|
417
|
+
if (done) break;
|
|
418
|
+
|
|
419
|
+
const result: CommandResult = value.data;
|
|
420
|
+
|
|
421
|
+
// Handle different stages
|
|
422
|
+
switch (result.stage) {
|
|
423
|
+
case CommandStage.SENT:
|
|
424
|
+
console.log('Command sent to bus');
|
|
425
|
+
break;
|
|
426
|
+
case CommandStage.PROCESSED:
|
|
427
|
+
console.log('Command processed by aggregate');
|
|
428
|
+
break;
|
|
429
|
+
case CommandStage.SNAPSHOT:
|
|
430
|
+
console.log('Snapshot generated');
|
|
431
|
+
break;
|
|
432
|
+
case CommandStage.PROJECTED:
|
|
433
|
+
console.log('Events projected to read model');
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
} finally {
|
|
438
|
+
reader.releaseLock();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Complex Query Building
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
import {
|
|
447
|
+
and,
|
|
448
|
+
or,
|
|
449
|
+
eq,
|
|
450
|
+
ne,
|
|
451
|
+
gt,
|
|
452
|
+
lt,
|
|
453
|
+
contains,
|
|
454
|
+
isIn,
|
|
455
|
+
notIn,
|
|
456
|
+
between,
|
|
457
|
+
startsWith,
|
|
458
|
+
endsWith,
|
|
459
|
+
elemMatch,
|
|
460
|
+
isNull,
|
|
461
|
+
notNull,
|
|
462
|
+
exists,
|
|
463
|
+
today,
|
|
464
|
+
thisWeek,
|
|
465
|
+
recentDays,
|
|
466
|
+
} from '@ahoo-wang/fetcher-wow';
|
|
467
|
+
|
|
468
|
+
// Build a complex query for user search
|
|
469
|
+
const userSearchQuery = {
|
|
470
|
+
condition: and(
|
|
471
|
+
eq('tenantId', 'tenant-123'),
|
|
472
|
+
ne('status', 'deleted'),
|
|
473
|
+
or(
|
|
474
|
+
// Search by name or email
|
|
475
|
+
contains('name', 'john'),
|
|
476
|
+
contains('email', 'john'),
|
|
477
|
+
),
|
|
478
|
+
// Age and score filters
|
|
479
|
+
gt('age', 18),
|
|
480
|
+
between('score', 50, 100),
|
|
481
|
+
|
|
482
|
+
// Department filters
|
|
483
|
+
isIn('departments', 'engineering', 'marketing'),
|
|
484
|
+
notIn('blockedDepartments', 'hr', 'finance'),
|
|
485
|
+
|
|
486
|
+
// String pattern matching
|
|
487
|
+
startsWith('employeeId', 'EMP-'),
|
|
488
|
+
endsWith('domain', '.com'),
|
|
489
|
+
|
|
490
|
+
// Array matching
|
|
491
|
+
elemMatch('roles', eq('name', 'admin')),
|
|
492
|
+
|
|
493
|
+
// Date filters
|
|
494
|
+
recentDays('lastLogin', 30),
|
|
495
|
+
thisWeek('createdAt'),
|
|
496
|
+
|
|
497
|
+
// Existence checks
|
|
498
|
+
exists('phoneNumber'),
|
|
499
|
+
notNull('address'),
|
|
500
|
+
),
|
|
501
|
+
|
|
502
|
+
sort: [
|
|
503
|
+
{ field: 'score', direction: 'DESC' },
|
|
504
|
+
{ field: 'lastLogin', direction: 'DESC' },
|
|
505
|
+
],
|
|
506
|
+
|
|
507
|
+
projection: {
|
|
508
|
+
include: ['id', 'name', 'email', 'score', 'lastLogin', 'departments'],
|
|
509
|
+
},
|
|
510
|
+
|
|
511
|
+
pagination: {
|
|
512
|
+
index: 1,
|
|
513
|
+
size: 50,
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## ๐งช Testing
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
# Run tests
|
|
522
|
+
pnpm test
|
|
523
|
+
|
|
524
|
+
# Run tests with coverage
|
|
525
|
+
pnpm test --coverage
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
## ๐ค Contributing
|
|
529
|
+
|
|
530
|
+
Contributions are welcome! Please see
|
|
531
|
+
the [contributing guide](https://github.com/Ahoo-Wang/fetcher/blob/main/CONTRIBUTING.md) for more details.
|
|
532
|
+
|
|
533
|
+
## ๐ License
|
|
534
|
+
|
|
535
|
+
Apache-2.0
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
<p align="center">
|
|
540
|
+
Part of the <a href="https://github.com/Ahoo-Wang/fetcher">Fetcher</a> ecosystem
|
|
541
|
+
</p>
|