@ahoo-wang/fetcher-generator 2.9.0 â 2.9.2
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 +207 -3
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -340,8 +340,7 @@ import {
|
|
|
340
340
|
export class CartApiClient implements ApiMetadataCapable {
|
|
341
341
|
constructor(
|
|
342
342
|
public readonly apiMetadata: ApiMetadata = { basePath: 'example' },
|
|
343
|
-
) {
|
|
344
|
-
}
|
|
343
|
+
) {}
|
|
345
344
|
|
|
346
345
|
/** Custom command sending */
|
|
347
346
|
@post('/cart/{userId}/customize-send-cmd')
|
|
@@ -397,7 +396,9 @@ const fetcher = new Fetcher({
|
|
|
397
396
|
});
|
|
398
397
|
|
|
399
398
|
// Use the generated query client factory
|
|
400
|
-
const snapshotClient = cartQueryClientFactory.createSnapshotQueryClient({
|
|
399
|
+
const snapshotClient = cartQueryClientFactory.createSnapshotQueryClient({
|
|
400
|
+
fetcher: fetcher,
|
|
401
|
+
});
|
|
401
402
|
const cartState = await snapshotClient.singleState({ condition: all() });
|
|
402
403
|
|
|
403
404
|
// Use the generated command client
|
|
@@ -419,6 +420,209 @@ const apiClient = new CartApiClient({ fetcher: fetcher });
|
|
|
419
420
|
const cartData = await apiClient.me();
|
|
420
421
|
```
|
|
421
422
|
|
|
423
|
+
## đ Advanced Usage Examples
|
|
424
|
+
|
|
425
|
+
### Custom Configuration
|
|
426
|
+
|
|
427
|
+
Create a `.fetcherrc.json` configuration file for advanced generation options:
|
|
428
|
+
|
|
429
|
+
```json
|
|
430
|
+
{
|
|
431
|
+
"generator": {
|
|
432
|
+
"targetFramework": "wow",
|
|
433
|
+
"outputFormat": "typescript",
|
|
434
|
+
"basePath": "api/v1",
|
|
435
|
+
"generateIndexFiles": true,
|
|
436
|
+
"verbose": true,
|
|
437
|
+
"typeMappings": {
|
|
438
|
+
"string": "string",
|
|
439
|
+
"integer": "number",
|
|
440
|
+
"boolean": "boolean",
|
|
441
|
+
"number": "number"
|
|
442
|
+
},
|
|
443
|
+
"schemaTransformers": [
|
|
444
|
+
{
|
|
445
|
+
"pattern": "^wow\\.",
|
|
446
|
+
"transform": "removePrefix"
|
|
447
|
+
}
|
|
448
|
+
]
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Multi-Context Generation
|
|
454
|
+
|
|
455
|
+
Generate code for multiple bounded contexts from a single OpenAPI spec:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
# Generate all contexts
|
|
459
|
+
fetcher-generator generate -i ./multi-context-api.json -o ./src/generated
|
|
460
|
+
|
|
461
|
+
# Generated structure:
|
|
462
|
+
# src/generated/
|
|
463
|
+
# âââ ecommerce/
|
|
464
|
+
# â âââ index.ts
|
|
465
|
+
# â âââ boundedContext.ts
|
|
466
|
+
# â âââ cart/
|
|
467
|
+
# â âââ order/
|
|
468
|
+
# â âââ product/
|
|
469
|
+
# âââ inventory/
|
|
470
|
+
# âââ index.ts
|
|
471
|
+
# âââ boundedContext.ts
|
|
472
|
+
# âââ stock/
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Event-Driven Architecture
|
|
476
|
+
|
|
477
|
+
Generated code supports event streaming for real-time updates:
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
import { cartQueryClientFactory } from './generated/ecommerce/cart/queryClient';
|
|
481
|
+
import { CartStreamCommandClient } from './generated/ecommerce/cart/commandClient';
|
|
482
|
+
|
|
483
|
+
// Create streaming query client
|
|
484
|
+
const eventClient = cartQueryClientFactory.createEventQueryClient({
|
|
485
|
+
fetcher: fetcher,
|
|
486
|
+
onEvent: event => {
|
|
487
|
+
console.log('Cart event:', event);
|
|
488
|
+
// Handle real-time cart updates
|
|
489
|
+
},
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Use streaming command client
|
|
493
|
+
const streamCommandClient = new CartStreamCommandClient({ fetcher });
|
|
494
|
+
|
|
495
|
+
const eventStream = await streamCommandClient.addCartItem(
|
|
496
|
+
{
|
|
497
|
+
command: { productId: 'item-123', quantity: 1 },
|
|
498
|
+
},
|
|
499
|
+
{ ownerId: 'user-456' },
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
// Process streaming events
|
|
503
|
+
for await (const event of eventStream) {
|
|
504
|
+
console.log('Command event:', event);
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Custom Type Guards and Validation
|
|
509
|
+
|
|
510
|
+
Generated code includes runtime type validation:
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
import { CartState, isCartState } from './generated/ecommerce/cart/types';
|
|
514
|
+
|
|
515
|
+
// Runtime type checking
|
|
516
|
+
function processCartData(data: unknown) {
|
|
517
|
+
if (isCartState(data)) {
|
|
518
|
+
// TypeScript knows data is CartState here
|
|
519
|
+
console.log('Valid cart state:', data.items.length);
|
|
520
|
+
} else {
|
|
521
|
+
throw new Error('Invalid cart data');
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Integration with State Management
|
|
527
|
+
|
|
528
|
+
Use generated clients with popular state management libraries:
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
// Redux Toolkit integration
|
|
532
|
+
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
|
533
|
+
import { cartQueryClientFactory } from './generated/ecommerce/cart/queryClient';
|
|
534
|
+
|
|
535
|
+
const fetchCartState = createAsyncThunk(
|
|
536
|
+
'cart/fetchState',
|
|
537
|
+
async (ownerId: string) => {
|
|
538
|
+
const snapshotClient = cartQueryClientFactory.createSnapshotQueryClient({
|
|
539
|
+
fetcher: fetcher,
|
|
540
|
+
});
|
|
541
|
+
return await snapshotClient.singleState({ condition: { ownerId } });
|
|
542
|
+
},
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
const cartSlice = createSlice({
|
|
546
|
+
name: 'cart',
|
|
547
|
+
initialState: { items: [], loading: false },
|
|
548
|
+
reducers: {},
|
|
549
|
+
extraReducers: builder => {
|
|
550
|
+
builder.addCase(fetchCartState.pending, state => {
|
|
551
|
+
state.loading = true;
|
|
552
|
+
});
|
|
553
|
+
builder.addCase(fetchCartState.fulfilled, (state, action) => {
|
|
554
|
+
state.items = action.payload.items;
|
|
555
|
+
state.loading = false;
|
|
556
|
+
});
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Error Handling and Retry Logic
|
|
562
|
+
|
|
563
|
+
Generated clients work seamlessly with retry mechanisms:
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
567
|
+
import { CartCommandClient } from './generated/ecommerce/cart/commandClient';
|
|
568
|
+
|
|
569
|
+
// Configure fetcher with retry logic
|
|
570
|
+
const fetcher = new Fetcher({
|
|
571
|
+
baseURL: 'https://api.example.com',
|
|
572
|
+
retryConfig: {
|
|
573
|
+
maxRetries: 3,
|
|
574
|
+
retryDelay: 1000,
|
|
575
|
+
retryCondition: error => error.status >= 500,
|
|
576
|
+
},
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const commandClient = new CartCommandClient({ fetcher });
|
|
580
|
+
|
|
581
|
+
// Commands automatically retry on server errors
|
|
582
|
+
try {
|
|
583
|
+
const result = await commandClient.addCartItem(
|
|
584
|
+
{
|
|
585
|
+
command: { productId: 'item-123', quantity: 1 },
|
|
586
|
+
},
|
|
587
|
+
{ ownerId: 'user-456' },
|
|
588
|
+
);
|
|
589
|
+
} catch (error) {
|
|
590
|
+
console.error('Failed to add item after retries:', error);
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Testing Generated Code
|
|
595
|
+
|
|
596
|
+
Write unit tests for generated clients:
|
|
597
|
+
|
|
598
|
+
```typescript
|
|
599
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
600
|
+
import { CartCommandClient } from './generated/ecommerce/cart/commandClient';
|
|
601
|
+
|
|
602
|
+
describe('CartCommandClient', () => {
|
|
603
|
+
it('should add item to cart', async () => {
|
|
604
|
+
const mockFetcher = {
|
|
605
|
+
request: vi.fn().mockResolvedValue({ success: true }),
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
const client = new CartCommandClient({
|
|
609
|
+
fetcher: mockFetcher as any,
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// This will throw autoGeneratedError in generated code
|
|
613
|
+
// In real usage, you'd use a decorator interceptor
|
|
614
|
+
expect(() =>
|
|
615
|
+
client.addCartItem(
|
|
616
|
+
{
|
|
617
|
+
command: { productId: 'test', quantity: 1 },
|
|
618
|
+
},
|
|
619
|
+
{ ownerId: 'user' },
|
|
620
|
+
),
|
|
621
|
+
).toThrow();
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
```
|
|
625
|
+
|
|
422
626
|
## đ OpenAPI Specification Requirements
|
|
423
627
|
|
|
424
628
|
The generator expects OpenAPI 3.0+ specifications with specific patterns for WOW domain-driven design framework:
|
package/dist/cli.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("commander"),a=require("./index.cjs");require("@ahoo-wang/fetcher");require("yaml");require("fs");require("path");class f{getTimestamp(){return new Date().toISOString().slice(11,19)}info(e,...t){const o=this.getTimestamp();t.length>0?console.log(`[${o}] âšī¸ ${e}`,...t):console.log(`[${o}] âšī¸ ${e}`)}success(e,...t){const o=this.getTimestamp();t.length>0?console.log(`[${o}] â
${e}`,...t):console.log(`[${o}] â
${e}`)}error(e,...t){const o=this.getTimestamp();t.length>0?console.error(`[${o}] â ${e}`,...t):console.error(`[${o}] â ${e}`)}progress(e,t=0,...o){const i=this.getTimestamp(),r=" ".repeat(t);o.length>0?console.log(`[${i}] đ ${r}${e}`,...o):console.log(`[${i}] đ ${r}${e}`)}progressWithCount(e,t,o,i=0,...r){const s=this.getTimestamp(),p=" ".repeat(i),l=`[${e}/${t}]`;r.length>0?console.log(`[${s}] đ ${p}${l} ${o}`,...r):console.log(`[${s}] đ ${p}${l} ${o}`)}}function h(n){if(!n)return!1;try{const e=new URL(n);return e.protocol==="http:"||e.protocol==="https:"}catch{return n.length>0}}async function d(n){const e=new f;process.on("SIGINT",()=>{e.error("Generation interrupted by user"),process.exit(130)}),h(n.input)||(e.error("Invalid input: must be a valid file path or HTTP/HTTPS URL"),process.exit(2));try{e.info("Starting code generation...");const t={inputPath:n.input,outputDir:n.output,configPath:n.config,tsConfigFilePath:n.tsConfigFilePath,logger:e};await new a.CodeGenerator(t).generate(),e.success(`Code generation completed successfully! Files generated in: ${n.output}`)}catch(t){e.error(`Error during code generation: ${t}`),process.exit(1)}}const $="2.9.
|
|
2
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("commander"),a=require("./index.cjs");require("@ahoo-wang/fetcher");require("yaml");require("fs");require("path");class f{getTimestamp(){return new Date().toISOString().slice(11,19)}info(e,...t){const o=this.getTimestamp();t.length>0?console.log(`[${o}] âšī¸ ${e}`,...t):console.log(`[${o}] âšī¸ ${e}`)}success(e,...t){const o=this.getTimestamp();t.length>0?console.log(`[${o}] â
${e}`,...t):console.log(`[${o}] â
${e}`)}error(e,...t){const o=this.getTimestamp();t.length>0?console.error(`[${o}] â ${e}`,...t):console.error(`[${o}] â ${e}`)}progress(e,t=0,...o){const i=this.getTimestamp(),r=" ".repeat(t);o.length>0?console.log(`[${i}] đ ${r}${e}`,...o):console.log(`[${i}] đ ${r}${e}`)}progressWithCount(e,t,o,i=0,...r){const s=this.getTimestamp(),p=" ".repeat(i),l=`[${e}/${t}]`;r.length>0?console.log(`[${s}] đ ${p}${l} ${o}`,...r):console.log(`[${s}] đ ${p}${l} ${o}`)}}function h(n){if(!n)return!1;try{const e=new URL(n);return e.protocol==="http:"||e.protocol==="https:"}catch{return n.length>0}}async function d(n){const e=new f;process.on("SIGINT",()=>{e.error("Generation interrupted by user"),process.exit(130)}),h(n.input)||(e.error("Invalid input: must be a valid file path or HTTP/HTTPS URL"),process.exit(2));try{e.info("Starting code generation...");const t={inputPath:n.input,outputDir:n.output,configPath:n.config,tsConfigFilePath:n.tsConfigFilePath,logger:e};await new a.CodeGenerator(t).generate(),e.success(`Code generation completed successfully! Files generated in: ${n.output}`)}catch(t){e.error(`Error during code generation: ${t}`),process.exit(1)}}const $="2.9.2",m={version:$};function u(){return c.program.name("fetcher-generator").description("OpenAPI Specification TypeScript code generator for Wow").version(m.version),c.program.command("generate").description("Generate TypeScript code from OpenAPI specification").requiredOption("-i, --input <file>","Input OpenAPI specification file path or URL (http/https)").option("-o, --output <path>","Output directory path","src/generated").option("-c, --config <file>","Configuration file path",a.DEFAULT_CONFIG_PATH).option("-t, --ts-config-file-path <file>","TypeScript configuration file path").option("-v, --verbose","Enable verbose logging").option("--dry-run","Show what would be generated without writing files").action(d),c.program}function g(){u().parse()}g();exports.runCLI=g;exports.setupCLI=u;
|
|
3
3
|
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.js
CHANGED