@ahoo-wang/fetcher-generator 2.1.1 β†’ 2.2.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 CHANGED
@@ -8,19 +8,28 @@
8
8
  [![npm bundle size](https://img.shields.io/bundlephobia/minzip/%40ahoo-wang%2Ffetcher-generator)](https://www.npmjs.com/package/@ahoo-wang/fetcher-generator)
9
9
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/Ahoo-Wang/fetcher)
10
10
 
11
- TypeScript code generator from OpenAPI specs for WOW domain-driven design framework. Generates type-safe models, query
11
+ TypeScript code generator from OpenAPI specs for [Wow](https://github.com/Ahoo-Wang/Wow) domain-driven design framework.
12
+ Generates type-safe models, query
12
13
  clients, and command clients from OpenAPI specifications.
13
14
 
15
+ **[Wow](https://github.com/Ahoo-Wang/Wow) Framework**: A domain-driven design framework that provides event sourcing,
16
+ CQRS (Command Query Responsibility
17
+ Segregation),
18
+ and aggregate patterns for building scalable distributed systems.
19
+
14
20
  ## 🌟 Features
15
21
 
16
- - **🎯 OpenAPI 3.0+ Support**: Full support for OpenAPI 3.0+ specifications
22
+ - **🎯 OpenAPI 3.0+ Support**: Full support for OpenAPI 3.0+ specifications (JSON/YAML)
17
23
  - **πŸ“¦ TypeScript Code Generation**: Generates type-safe TypeScript interfaces, enums, and classes
18
- - **πŸ—οΈ Domain-Driven Design**: Specialized for WOW framework with aggregates, commands, and queries
24
+ - **πŸ—οΈ Domain-Driven Design**: Specialized for WOW framework with aggregates, commands, queries, and events
19
25
  - **πŸ”§ CLI Tool**: Easy-to-use command-line interface for code generation
20
26
  - **🎨 Decorator-Based APIs**: Generates decorator-based client classes for clean API interactions
21
- - **πŸ“‹ Comprehensive Models**: Handles complex schemas including unions, intersections, and references
22
- - **πŸš€ Fetcher Integration**: Seamlessly integrates with the Fetcher ecosystem
23
- - **πŸ“Š Progress Logging**: Friendly logging with progress indicators and emojis
27
+ - **πŸ“‹ Comprehensive Models**: Handles complex schemas including unions, intersections, enums, and references
28
+ - **πŸš€ Fetcher Integration**: Seamlessly integrates with the Fetcher ecosystem packages
29
+ - **πŸ“Š Progress Logging**: Friendly logging with progress indicators during generation
30
+ - **πŸ“ Auto Index Generation**: Automatically generates index.ts files for clean module organization
31
+ - **🌐 Remote Spec Support**: Load OpenAPI specs directly from HTTP/HTTPS URLs
32
+ - **🎭 Event Streaming**: Generates both regular and event-stream command clients
24
33
 
25
34
  ## πŸš€ Quick Start
26
35
 
@@ -57,7 +66,10 @@ fetcher-generator generate [options]
57
66
  - `-i, --input <path>`: Input OpenAPI specification file path or URL (required)
58
67
  - Supports local file paths (e.g., `./api-spec.json`, `/path/to/spec.yaml`)
59
68
  - Supports HTTP/HTTPS URLs (e.g., `https://api.example.com/openapi.json`)
60
- - `-o, --output <path>`: Output directory path (required)
69
+ - `-o, --output <path>`: Output directory path (default: `src/generated`)
70
+ - `-c, --config <file>`: Configuration file path (optional)
71
+ - `-v, --verbose`: Enable verbose logging during generation
72
+ - `--dry-run`: Show what would be generated without writing files (reserved for future use)
61
73
  - `-h, --help`: Display help information
62
74
  - `-V, --version`: Display version number
63
75
 
@@ -84,13 +96,37 @@ The generator creates the following structure in your output directory:
84
96
  ```
85
97
  output/
86
98
  β”œβ”€β”€ {bounded-context}/
87
- β”‚ β”œβ”€β”€ boundedContext.ts # Bounded context constants
99
+ β”‚ β”œβ”€β”€ index.ts # Auto-generated index file exporting all aggregates
100
+ β”‚ β”œβ”€β”€ boundedContext.ts # Bounded context alias constant
88
101
  β”‚ β”œβ”€β”€ types.ts # Shared types for the bounded context
89
102
  β”‚ └── {aggregate}/ # Aggregate-specific files
90
- β”‚ β”œβ”€β”€ types.ts # Aggregate-specific types and models
91
- β”‚ β”œβ”€β”€ queryClient.ts # Query client classes
92
- β”‚ └── commandClient.ts # Command client classes
93
- └── tsconfig.json # TypeScript configuration
103
+ β”‚ β”œβ”€β”€ index.ts # Auto-generated index file for aggregate
104
+ β”‚ β”œβ”€β”€ types.ts # Aggregate-specific types, models, and enums
105
+ β”‚ β”œβ”€β”€ queryClient.ts # Query client factory for state and event queries
106
+ β”‚ └── commandClient.ts # Command client classes (regular and streaming)
107
+ β”œβ”€β”€ index.ts # Root index file exporting all bounded contexts
108
+ └── tsconfig.json # TypeScript configuration for generated code
109
+ ```
110
+
111
+ #### Index File Generation
112
+
113
+ The generator automatically creates `index.ts` files in all directories to provide convenient module exports:
114
+
115
+ - **Root index.ts**: Exports all bounded contexts
116
+ - **Bounded context index.ts**: Exports all aggregates within the context
117
+ - **Aggregate index.ts**: Exports all files within the aggregate
118
+
119
+ This allows for clean imports like:
120
+
121
+ ```typescript
122
+ // Import everything from a bounded context
123
+ import * as compensation from './generated/compensation';
124
+
125
+ // Import specific aggregates
126
+ import { executionFailed } from './generated/compensation';
127
+
128
+ // Import specific files
129
+ import { ExecutionFailedState } from './generated/compensation/execution_failed';
94
130
  ```
95
131
 
96
132
  ## 🎯 Generated Code Examples
@@ -128,21 +164,25 @@ import {
128
164
  QueryClientOptions,
129
165
  ResourceAttributionPathSpec,
130
166
  } from '@ahoo-wang/fetcher-wow';
167
+ import {
168
+ CartAggregatedFields,
169
+ CartItemAdded,
170
+ CartItemRemoved,
171
+ CartQuantityChanged,
172
+ CartState,
173
+ } from './types';
131
174
 
132
175
  const DEFAULT_QUERY_CLIENT_OPTIONS: QueryClientOptions = {
133
- contextAlias: 'compensation',
134
- aggregateName: 'execution_failed',
135
- resourceAttribution: ResourceAttributionPathSpec.NONE,
176
+ contextAlias: 'example',
177
+ aggregateName: 'cart',
178
+ resourceAttribution: ResourceAttributionPathSpec.OWNER,
136
179
  };
137
180
 
138
- type DOMAIN_EVENT_TYPES =
139
- | CompensationPrepared
140
- | ExecutionFailedApplied
141
- | ExecutionFailedCreated;
181
+ type DOMAIN_EVENT_TYPES = CartItemAdded | CartItemRemoved | CartQuantityChanged;
142
182
 
143
- export const executionFailedQueryClientFactory = new QueryClientFactory<
144
- ExecutionFailedState,
145
- ExecutionFailedAggregatedFields | string,
183
+ export const cartQueryClientFactory = new QueryClientFactory<
184
+ CartState,
185
+ CartAggregatedFields | string,
146
186
  DOMAIN_EVENT_TYPES
147
187
  >(DEFAULT_QUERY_CLIENT_OPTIONS);
148
188
  ```
@@ -151,56 +191,130 @@ export const executionFailedQueryClientFactory = new QueryClientFactory<
151
191
 
152
192
  ```typescript
153
193
  // Generated command client with decorator-based API
194
+ import { ContentTypeValues } from '@ahoo-wang/fetcher';
154
195
  import {
196
+ type ApiMetadata,
197
+ type ApiMetadataCapable,
155
198
  api,
199
+ attribute,
200
+ autoGeneratedError,
201
+ del,
202
+ path,
156
203
  post,
157
204
  put,
158
- path,
159
205
  request,
160
- attribute,
161
- autoGeneratedError,
162
206
  } from '@ahoo-wang/fetcher-decorator';
207
+ import { JsonEventStreamResultExtractor } from '@ahoo-wang/fetcher-eventstream';
208
+ import type {
209
+ CommandRequest,
210
+ CommandResult,
211
+ CommandResultEventStream,
212
+ DeleteAggregate,
213
+ RecoverAggregate,
214
+ } from '@ahoo-wang/fetcher-wow';
215
+ import {
216
+ AddCartItem,
217
+ ChangeQuantity,
218
+ MockVariableCommand,
219
+ MountedCommand,
220
+ RemoveCartItem,
221
+ ViewCart,
222
+ } from './types';
223
+
224
+ enum COMMAND_ENDPOINT_PATHS {
225
+ VIEW_CART = '/owner/{ownerId}/cart/view_cart',
226
+ ADD_CART_ITEM = '/owner/{ownerId}/cart/add_cart_item',
227
+ CHANGE_QUANTITY = '/owner/{ownerId}/cart/change_quantity',
228
+ REMOVE_CART_ITEM = '/owner/{ownerId}/cart/remove_cart_item',
229
+ MOUNTED_COMMAND = '/owner/{ownerId}/cart/mounted_command',
230
+ MOCK_VARIABLE_COMMAND = '/tenant/{tenantId}/owner/{ownerId}/cart/{id}/{customerId}/{mockEnum}',
231
+ DEFAULT_DELETE_AGGREGATE = '/owner/{ownerId}/cart',
232
+ DEFAULT_RECOVER_AGGREGATE = '/owner/{ownerId}/cart/recover',
233
+ }
163
234
 
164
- const COMMAND_ENDPOINT_PATHS = {
165
- CREATE_EXECUTION_FAILED: '/execution_failed',
166
- PREPARE_COMPENSATION: '/execution_failed/{id}/prepare_compensation',
167
- } as const;
235
+ const DEFAULT_COMMAND_CLIENT_OPTIONS: ApiMetadata = {
236
+ basePath: 'example',
237
+ };
168
238
 
169
239
  @api()
170
- export class ExecutionFailedCommandClient implements ApiMetadataCapable {
240
+ export class CartCommandClient implements ApiMetadataCapable {
171
241
  constructor(
172
- public readonly apiMetadata: ApiMetadata = { basePath: 'compensation' },
242
+ public readonly apiMetadata: ApiMetadata = DEFAULT_COMMAND_CLIENT_OPTIONS,
173
243
  ) {}
174
244
 
175
- /** create_execution_failed */
176
- @post(COMMAND_ENDPOINT_PATHS.CREATE_EXECUTION_FAILED)
177
- createExecutionFailed(
178
- @request() commandRequest: CommandRequest<CreateExecutionFailed>,
245
+ /** view_cart */
246
+ @put(COMMAND_ENDPOINT_PATHS.VIEW_CART)
247
+ viewCart(
248
+ @request() commandRequest: CommandRequest<ViewCart>,
249
+ @attribute() attributes: Record<string, any>,
250
+ ): Promise<CommandResult> {
251
+ throw autoGeneratedError(commandRequest, attributes);
252
+ }
253
+
254
+ /**
255
+ * 加ε…₯购物车
256
+ * 加ε…₯购物车
257
+ */
258
+ @post(COMMAND_ENDPOINT_PATHS.ADD_CART_ITEM)
259
+ addCartItem(
260
+ @request() commandRequest: CommandRequest<AddCartItem>,
179
261
  @attribute() attributes: Record<string, any>,
180
262
  ): Promise<CommandResult> {
181
263
  throw autoGeneratedError(commandRequest, attributes);
182
264
  }
183
265
 
184
- /** prepare_compensation */
185
- @put(COMMAND_ENDPOINT_PATHS.PREPARE_COMPENSATION)
186
- prepareCompensation(
187
- @path('id') id: string,
188
- @request() commandRequest: CommandRequest<PrepareCompensation>,
266
+ /** ε˜ζ›΄θ΄­δΉ°ζ•°ι‡ */
267
+ @put(COMMAND_ENDPOINT_PATHS.CHANGE_QUANTITY)
268
+ changeQuantity(
269
+ @request() commandRequest: CommandRequest<ChangeQuantity>,
189
270
  @attribute() attributes: Record<string, any>,
190
271
  ): Promise<CommandResult> {
191
- throw autoGeneratedError(id, commandRequest, attributes);
272
+ throw autoGeneratedError(commandRequest, attributes);
273
+ }
274
+
275
+ /** εˆ ι™€ε•†ε“ */
276
+ @put(COMMAND_ENDPOINT_PATHS.REMOVE_CART_ITEM)
277
+ removeCartItem(
278
+ @request() commandRequest: CommandRequest<RemoveCartItem>,
279
+ @attribute() attributes: Record<string, any>,
280
+ ): Promise<CommandResult> {
281
+ throw autoGeneratedError(commandRequest, attributes);
192
282
  }
193
283
  }
194
284
  ```
195
285
 
286
+ The generator also creates streaming command clients for event-driven interactions:
287
+
288
+ ```typescript
289
+ @api('', {
290
+ headers: { Accept: ContentTypeValues.TEXT_EVENT_STREAM },
291
+ resultExtractor: JsonEventStreamResultExtractor,
292
+ })
293
+ export class CartStreamCommandClient implements ApiMetadataCapable {
294
+ constructor(
295
+ public readonly apiMetadata: ApiMetadata = DEFAULT_COMMAND_CLIENT_OPTIONS,
296
+ ) {}
297
+
298
+ /** view_cart */
299
+ @put(COMMAND_ENDPOINT_PATHS.VIEW_CART)
300
+ viewCart(
301
+ @request() commandRequest: CommandRequest<ViewCart>,
302
+ @attribute() attributes: Record<string, any>,
303
+ ): Promise<CommandResultEventStream> {
304
+ throw autoGeneratedError(commandRequest, attributes);
305
+ }
306
+ // ... other streaming methods
307
+ }
308
+ ```
309
+
196
310
  ## πŸ”§ Integration with Fetcher
197
311
 
198
312
  The generated code is designed to work seamlessly with the Fetcher ecosystem:
199
313
 
200
314
  ```typescript
201
315
  import { Fetcher } from '@ahoo-wang/fetcher';
202
- import { executionFailedQueryClientFactory } from './generated/compensation/execution_failed/queryClient';
203
- import { ExecutionFailedCommandClient } from './generated/compensation/execution_failed/commandClient';
316
+ import { cartQueryClientFactory } from './generated/example/cart/queryClient';
317
+ import { CartCommandClient } from './generated/example/cart/commandClient';
204
318
 
205
319
  // Create a fetcher instance
206
320
  const fetcher = new Fetcher({
@@ -211,43 +325,55 @@ const fetcher = new Fetcher({
211
325
  Fetcher.register('api', fetcher);
212
326
 
213
327
  // Use the generated query client factory
214
- const queryClient = executionFailedQueryClientFactory.createQueryClient();
215
- const state = await queryClient.loadAggregate('aggregate-id');
328
+ const queryClient = cartQueryClientFactory.createQueryClient();
329
+ const cartState = await queryClient.loadAggregate('cart-id');
216
330
 
217
331
  // Use the generated command client
218
- const commandClient = new ExecutionFailedCommandClient();
219
- const result = await commandClient.createExecutionFailed(
332
+ const commandClient = new CartCommandClient();
333
+ const result = await commandClient.addCartItem(
220
334
  {
221
335
  command: {
222
- /* command data */
336
+ productId: 'product-123',
337
+ quantity: 2,
223
338
  },
224
339
  },
225
340
  {
226
- /* attributes */
341
+ ownerId: 'user-456',
227
342
  },
228
343
  );
229
344
  ```
230
345
 
231
346
  ## πŸ“‹ OpenAPI Specification Requirements
232
347
 
233
- The generator expects OpenAPI 3.0+ specifications with specific patterns for WOW framework:
348
+ The generator expects OpenAPI 3.0+ specifications with specific patterns for WOW domain-driven design framework:
234
349
 
235
350
  ### Aggregate Definition
236
351
 
237
- Aggregates are identified by operation IDs following the pattern:
352
+ Aggregates are identified by operation tags that follow the pattern:
353
+
354
+ - `{context}.{aggregate}`
238
355
 
239
- - `{context}.{aggregate}.*`
356
+ ### Operation Patterns
357
+
358
+ The generator recognizes operations by their `operationId` suffixes:
359
+
360
+ - **State Snapshots**: Operations ending with `.snapshot_state.single`
361
+ - **Event Queries**: Operations ending with `.event.list_query`
362
+ - **Field Queries**: Operations ending with `.snapshot.count`
363
+ - **Commands**: Any operation with a valid command request/response structure
240
364
 
241
365
  ### Commands and Queries
242
366
 
243
- - **Commands**: Operations with `POST`, `PUT`, `DELETE` methods
244
- - **Queries**: Operations with `GET` method
245
- - **Events**: Operations returning event streams
367
+ - **Commands**: Operations with `POST`, `PUT`, `DELETE` methods that return `wow.CommandOk` responses
368
+ - **Queries**: Operations with `GET` method for retrieving aggregate state or events
369
+ - **Events**: Operations returning event stream arrays with domain event structures
246
370
 
247
- ### Schema Naming
371
+ ### Schema Conventions
248
372
 
249
373
  - Use descriptive names for schemas
250
- - Avoid `wow.` prefixed schemas (reserved for internal use)
374
+ - Avoid `wow.` prefixed schemas (reserved for internal framework schemas)
375
+ - Command request bodies should reference schemas in `components/schemas`
376
+ - State and event schemas should follow the expected structure for domain modeling
251
377
 
252
378
  ## πŸ› οΈ Development
253
379
 
@@ -267,8 +393,14 @@ pnpm lint
267
393
  ### Testing the Generator
268
394
 
269
395
  ```bash
270
- # Generate test output
396
+ # Generate test output using the demo spec
271
397
  pnpm generate
398
+
399
+ # Run tests
400
+ pnpm test
401
+
402
+ # Run tests with coverage
403
+ pnpm test -- --coverage
272
404
  ```
273
405
 
274
406
  ## 🀝 Contributing