@ahoo-wang/fetcher-generator 2.5.0 → 2.5.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 +84 -6
- package/README.zh-CN.md +82 -6
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/client/apiClientGenerator.d.ts.map +1 -1
- package/dist/client/commandClientGenerator.d.ts +2 -7
- package/dist/client/commandClientGenerator.d.ts.map +1 -1
- package/dist/generateContext.d.ts +1 -0
- package/dist/generateContext.d.ts.map +1 -1
- package/dist/index.cjs +6 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +426 -414
- package/dist/index.js.map +1 -1
- package/dist/utils/operations.d.ts +15 -1
- package/dist/utils/operations.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,9 +96,10 @@ The generator creates the following structure in your output directory:
|
|
|
96
96
|
```
|
|
97
97
|
output/
|
|
98
98
|
├── {bounded-context}/
|
|
99
|
-
│ ├── index.ts # Auto-generated index file exporting all aggregates
|
|
99
|
+
│ ├── index.ts # Auto-generated index file exporting all aggregates and API clients
|
|
100
100
|
│ ├── boundedContext.ts # Bounded context alias constant
|
|
101
101
|
│ ├── types.ts # Shared types for the bounded context
|
|
102
|
+
│ ├── {Tag}ApiClient.ts # API client classes for custom endpoints (one per OpenAPI tag)
|
|
102
103
|
│ └── {aggregate}/ # Aggregate-specific files
|
|
103
104
|
│ ├── index.ts # Auto-generated index file for aggregate
|
|
104
105
|
│ ├── types.ts # Aggregate-specific types, models, and enums
|
|
@@ -113,20 +114,20 @@ output/
|
|
|
113
114
|
The generator automatically creates `index.ts` files in all directories to provide convenient module exports:
|
|
114
115
|
|
|
115
116
|
- **Root index.ts**: Exports all bounded contexts
|
|
116
|
-
- **Bounded context index.ts**: Exports all aggregates within the context
|
|
117
|
+
- **Bounded context index.ts**: Exports all aggregates and API clients (based on OpenAPI tags) within the context
|
|
117
118
|
- **Aggregate index.ts**: Exports all files within the aggregate
|
|
118
119
|
|
|
119
120
|
This allows for clean imports like:
|
|
120
121
|
|
|
121
122
|
```typescript
|
|
122
123
|
// Import everything from a bounded context
|
|
123
|
-
import * as
|
|
124
|
+
import * as example from './generated/example';
|
|
124
125
|
|
|
125
|
-
// Import specific aggregates
|
|
126
|
-
import {
|
|
126
|
+
// Import specific aggregates and API clients (API clients are generated per OpenAPI tag)
|
|
127
|
+
import { cart, CartApiClient } from './generated/example';
|
|
127
128
|
|
|
128
129
|
// Import specific files
|
|
129
|
-
import {
|
|
130
|
+
import { CartState } from './generated/example/cart';
|
|
130
131
|
```
|
|
131
132
|
|
|
132
133
|
## 🎯 Generated Code Examples
|
|
@@ -307,6 +308,78 @@ export class CartStreamCommandClient implements ApiMetadataCapable {
|
|
|
307
308
|
}
|
|
308
309
|
```
|
|
309
310
|
|
|
311
|
+
### API Clients
|
|
312
|
+
|
|
313
|
+
The generator also creates API client classes for custom endpoints that don't follow the domain-driven command pattern.
|
|
314
|
+
These are generated based on OpenAPI tags (one client class per tag):
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
// Generated API client for custom endpoints
|
|
318
|
+
import {
|
|
319
|
+
type ApiMetadata,
|
|
320
|
+
type ApiMetadataCapable,
|
|
321
|
+
ParameterRequest,
|
|
322
|
+
api,
|
|
323
|
+
attribute,
|
|
324
|
+
autoGeneratedError,
|
|
325
|
+
get,
|
|
326
|
+
path,
|
|
327
|
+
post,
|
|
328
|
+
request,
|
|
329
|
+
} from '@ahoo-wang/fetcher-decorator';
|
|
330
|
+
import { CommandResult } from '@ahoo-wang/fetcher-wow';
|
|
331
|
+
import { CartData } from './cart/types';
|
|
332
|
+
import { ContentTypeValues } from '@ahoo-wang/fetcher';
|
|
333
|
+
import {
|
|
334
|
+
JsonEventStreamResultExtractor,
|
|
335
|
+
JsonServerSentEventStream,
|
|
336
|
+
} from '@ahoo-wang/fetcher-eventstream';
|
|
337
|
+
|
|
338
|
+
/** Shopping Cart */
|
|
339
|
+
@api()
|
|
340
|
+
export class CartApiClient implements ApiMetadataCapable {
|
|
341
|
+
constructor(
|
|
342
|
+
public readonly apiMetadata: ApiMetadata = { basePath: 'example' },
|
|
343
|
+
) {
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Custom command sending */
|
|
347
|
+
@post('/cart/{userId}/customize-send-cmd')
|
|
348
|
+
customizeSendCmd(
|
|
349
|
+
@path('userId') userId: string,
|
|
350
|
+
@request() httpRequest?: ParameterRequest,
|
|
351
|
+
@attribute() attributes?: Record<string, any>,
|
|
352
|
+
): Promise<CommandResult> {
|
|
353
|
+
throw autoGeneratedError(userId, httpRequest, attributes);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Add cart item with event streaming */
|
|
357
|
+
@post('/cart/{userId}/add-cart-item', {
|
|
358
|
+
headers: { Accept: ContentTypeValues.TEXT_EVENT_STREAM },
|
|
359
|
+
resultExtractor: JsonEventStreamResultExtractor,
|
|
360
|
+
})
|
|
361
|
+
addCartItem(
|
|
362
|
+
@path('userId') userId: string,
|
|
363
|
+
@request() httpRequest?: ParameterRequest,
|
|
364
|
+
@attribute() attributes?: Record<string, any>,
|
|
365
|
+
): Promise<JsonServerSentEventStream<CommandResult>> {
|
|
366
|
+
throw autoGeneratedError(userId, httpRequest, attributes);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Get current user's cart */
|
|
370
|
+
@get('/cart/me')
|
|
371
|
+
me(): Promise<CartData> {
|
|
372
|
+
throw autoGeneratedError();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Get current user's cart (sync) */
|
|
376
|
+
@get('/cart/me/sync')
|
|
377
|
+
meSync(): Promise<CartData> {
|
|
378
|
+
throw autoGeneratedError();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
310
383
|
## 🔧 Integration with Fetcher
|
|
311
384
|
|
|
312
385
|
The generated code is designed to work seamlessly with the Fetcher ecosystem:
|
|
@@ -315,6 +388,7 @@ The generated code is designed to work seamlessly with the Fetcher ecosystem:
|
|
|
315
388
|
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
316
389
|
import { cartQueryClientFactory } from './generated/example/cart/queryClient';
|
|
317
390
|
import { CartCommandClient } from './generated/example/cart/commandClient';
|
|
391
|
+
import { CartApiClient } from './generated/example/CartApiClient';
|
|
318
392
|
|
|
319
393
|
// Create a fetcher instance
|
|
320
394
|
const fetcher = new Fetcher({
|
|
@@ -341,6 +415,10 @@ const result = await commandClient.addCartItem(
|
|
|
341
415
|
ownerId: 'user-456',
|
|
342
416
|
},
|
|
343
417
|
);
|
|
418
|
+
|
|
419
|
+
// Use the generated API client for custom endpoints (based on OpenAPI tag "cart")
|
|
420
|
+
const apiClient = new CartApiClient();
|
|
421
|
+
const cartData = await apiClient.me();
|
|
344
422
|
```
|
|
345
423
|
|
|
346
424
|
## 📋 OpenAPI Specification Requirements
|
package/README.zh-CN.md
CHANGED
|
@@ -90,9 +90,10 @@ fetcher-generator generate -i http://localhost:8080/api-spec.yaml -o ./src/gener
|
|
|
90
90
|
```
|
|
91
91
|
output/
|
|
92
92
|
├── {bounded-context}/
|
|
93
|
-
│ ├── index.ts #
|
|
93
|
+
│ ├── index.ts # 自动生成的索引文件,导出所有聚合和 API 客户端
|
|
94
94
|
│ ├── boundedContext.ts # 有界上下文别名常量
|
|
95
95
|
│ ├── types.ts # 有界上下文的共享类型
|
|
96
|
+
│ ├── {Tag}ApiClient.ts # API 客户端类,用于自定义端点(每个 OpenAPI 标签一个)
|
|
96
97
|
│ └── {aggregate}/ # 聚合特定文件
|
|
97
98
|
│ ├── index.ts # 聚合的自动生成索引文件
|
|
98
99
|
│ ├── types.ts # 聚合特定类型、模型和枚举
|
|
@@ -107,20 +108,20 @@ output/
|
|
|
107
108
|
生成器自动创建 `index.ts` 文件,为便捷的模块导出提供支持:
|
|
108
109
|
|
|
109
110
|
- **根 index.ts**:导出所有有界上下文
|
|
110
|
-
- **有界上下文 index.ts
|
|
111
|
+
- **有界上下文 index.ts**:导出上下文中的所有聚合和 API 客户端
|
|
111
112
|
- **聚合 index.ts**:导出聚合中的所有文件
|
|
112
113
|
|
|
113
114
|
这允许干净的导入,例如:
|
|
114
115
|
|
|
115
116
|
```typescript
|
|
116
117
|
// 导入有界上下文的所有内容
|
|
117
|
-
import * as
|
|
118
|
+
import * as example from './generated/example';
|
|
118
119
|
|
|
119
|
-
//
|
|
120
|
-
import {
|
|
120
|
+
// 导入特定聚合和 API 客户端
|
|
121
|
+
import { cart, CartApiClient } from './generated/example';
|
|
121
122
|
|
|
122
123
|
// 导入特定文件
|
|
123
|
-
import {
|
|
124
|
+
import { CartState } from './generated/example/cart';
|
|
124
125
|
```
|
|
125
126
|
|
|
126
127
|
## 🎯 生成的代码示例
|
|
@@ -301,6 +302,76 @@ export class CartStreamCommandClient implements ApiMetadataCapable {
|
|
|
301
302
|
}
|
|
302
303
|
```
|
|
303
304
|
|
|
305
|
+
### API 客户端
|
|
306
|
+
|
|
307
|
+
生成器还为不遵循领域驱动命令模式的自定义端点创建 API 客户端类。这些基于 OpenAPI 标签生成(每个标签一个客户端类):
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
// 生成的 API 客户端,用于自定义端点
|
|
311
|
+
import {
|
|
312
|
+
type ApiMetadata,
|
|
313
|
+
type ApiMetadataCapable,
|
|
314
|
+
ParameterRequest,
|
|
315
|
+
api,
|
|
316
|
+
attribute,
|
|
317
|
+
autoGeneratedError,
|
|
318
|
+
get,
|
|
319
|
+
path,
|
|
320
|
+
post,
|
|
321
|
+
request,
|
|
322
|
+
} from '@ahoo-wang/fetcher-decorator';
|
|
323
|
+
import { CommandResult } from '@ahoo-wang/fetcher-wow';
|
|
324
|
+
import { CartData } from './cart/types';
|
|
325
|
+
import { ContentTypeValues } from '@ahoo-wang/fetcher';
|
|
326
|
+
import {
|
|
327
|
+
JsonEventStreamResultExtractor,
|
|
328
|
+
JsonServerSentEventStream,
|
|
329
|
+
} from '@ahoo-wang/fetcher-eventstream';
|
|
330
|
+
|
|
331
|
+
/** 购物车 */
|
|
332
|
+
@api()
|
|
333
|
+
export class CartApiClient implements ApiMetadataCapable {
|
|
334
|
+
constructor(
|
|
335
|
+
public readonly apiMetadata: ApiMetadata = { basePath: 'example' },
|
|
336
|
+
) {}
|
|
337
|
+
|
|
338
|
+
/** 自定义发送命令 */
|
|
339
|
+
@post('/cart/{userId}/customize-send-cmd')
|
|
340
|
+
customizeSendCmd(
|
|
341
|
+
@path('userId') userId: string,
|
|
342
|
+
@request() httpRequest?: ParameterRequest,
|
|
343
|
+
@attribute() attributes?: Record<string, any>,
|
|
344
|
+
): Promise<CommandResult> {
|
|
345
|
+
throw autoGeneratedError(userId, httpRequest, attributes);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** 加入购物车(带事件流) */
|
|
349
|
+
@post('/cart/{userId}/add-cart-item', {
|
|
350
|
+
headers: { Accept: ContentTypeValues.TEXT_EVENT_STREAM },
|
|
351
|
+
resultExtractor: JsonEventStreamResultExtractor,
|
|
352
|
+
})
|
|
353
|
+
addCartItem(
|
|
354
|
+
@path('userId') userId: string,
|
|
355
|
+
@request() httpRequest?: ParameterRequest,
|
|
356
|
+
@attribute() attributes?: Record<string, any>,
|
|
357
|
+
): Promise<JsonServerSentEventStream<CommandResult>> {
|
|
358
|
+
throw autoGeneratedError(userId, httpRequest, attributes);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** 获取当前用户的购物车 */
|
|
362
|
+
@get('/cart/me')
|
|
363
|
+
me(): Promise<CartData> {
|
|
364
|
+
throw autoGeneratedError();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** 获取当前用户的购物车(同步) */
|
|
368
|
+
@get('/cart/me/sync')
|
|
369
|
+
meSync(): Promise<CartData> {
|
|
370
|
+
throw autoGeneratedError();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
304
375
|
## 🔧 与 Fetcher 集成
|
|
305
376
|
|
|
306
377
|
生成的代码设计为与 Fetcher 生态系统无缝集成:
|
|
@@ -309,6 +380,7 @@ export class CartStreamCommandClient implements ApiMetadataCapable {
|
|
|
309
380
|
import { Fetcher } from '@ahoo-wang/fetcher';
|
|
310
381
|
import { cartQueryClientFactory } from './generated/example/cart/queryClient';
|
|
311
382
|
import { CartCommandClient } from './generated/example/cart/commandClient';
|
|
383
|
+
import { CartApiClient } from './generated/example/CartApiClient';
|
|
312
384
|
|
|
313
385
|
// 创建 fetcher 实例
|
|
314
386
|
const fetcher = new Fetcher({
|
|
@@ -335,6 +407,10 @@ const result = await commandClient.addCartItem(
|
|
|
335
407
|
ownerId: 'user-456',
|
|
336
408
|
},
|
|
337
409
|
);
|
|
410
|
+
|
|
411
|
+
// 使用生成的 API 客户端用于自定义端点(基于 OpenAPI 标签 "cart")
|
|
412
|
+
const apiClient = new CartApiClient();
|
|
413
|
+
const cartData = await apiClient.me();
|
|
338
414
|
```
|
|
339
415
|
|
|
340
416
|
## 📋 OpenAPI 规范要求
|
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"),l=require("./index.cjs"),d=require("ts-morph");require("yaml");require("fs");require("@ahoo-wang/fetcher");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 n=this.getTimestamp(),i=" ".repeat(t);o.length>0?console.log(`[${n}] 🔄 ${i}${e}`,...o):console.log(`[${n}] 🔄 ${i}${e}`)}progressWithCount(e,t,o,n=0,...i){const s=this.getTimestamp(),p=" ".repeat(n),a=`[${e}/${t}]`;i.length>0?console.log(`[${s}] 🔄 ${p}${a} ${o}`,...i):console.log(`[${s}] 🔄 ${p}${a} ${o}`)}}function h(r){if(!r)return!1;try{const e=new URL(r);return e.protocol==="http:"||e.protocol==="https:"}catch{return r.length>0}}async function $(r){const e=new f;process.on("SIGINT",()=>{e.error("Generation interrupted by user"),process.exit(130)}),h(r.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=new d.Project,o={inputPath:r.input,outputDir:r.output,configPath:r.config,project:t,logger:e};await new l.CodeGenerator(o).generate(),e.success(`Code generation completed successfully! Files generated in: ${r.output}`)}catch(t){e.error(`Error during code generation: ${t}`),process.exit(1)}}const m="2.5.
|
|
2
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("commander"),l=require("./index.cjs"),d=require("ts-morph");require("yaml");require("fs");require("@ahoo-wang/fetcher");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 n=this.getTimestamp(),i=" ".repeat(t);o.length>0?console.log(`[${n}] 🔄 ${i}${e}`,...o):console.log(`[${n}] 🔄 ${i}${e}`)}progressWithCount(e,t,o,n=0,...i){const s=this.getTimestamp(),p=" ".repeat(n),a=`[${e}/${t}]`;i.length>0?console.log(`[${s}] 🔄 ${p}${a} ${o}`,...i):console.log(`[${s}] 🔄 ${p}${a} ${o}`)}}function h(r){if(!r)return!1;try{const e=new URL(r);return e.protocol==="http:"||e.protocol==="https:"}catch{return r.length>0}}async function $(r){const e=new f;process.on("SIGINT",()=>{e.error("Generation interrupted by user"),process.exit(130)}),h(r.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=new d.Project,o={inputPath:r.input,outputDir:r.output,configPath:r.config,project:t,logger:e};await new l.CodeGenerator(o).generate(),e.success(`Code generation completed successfully! Files generated in: ${r.output}`)}catch(t){e.error(`Error during code generation: ${t}`),process.exit(1)}}const m="2.5.2",T={version:m};function u(){return c.program.name("fetcher-generator").description("OpenAPI Specification TypeScript code generator for Wow").version(T.version),c.program.command("generate").description("Generate TypeScript code from OpenAPI specification").requiredOption("-i, --input <path>","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",l.DEFAULT_CONFIG_PATH).option("-v, --verbose","Enable verbose logging").option("--dry-run","Show what would be generated without writing files").action($),c.program}function g(){u().parse()}g();exports.runCLI=g;exports.setupCLI=u;
|
|
3
3
|
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiClientGenerator.d.ts","sourceRoot":"","sources":["../../src/client/apiClientGenerator.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"apiClientGenerator.d.ts","sourceRoot":"","sources":["../../src/client/apiClientGenerator.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAoDhE;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,SAAS;aAUtB,OAAO,EAAE,eAAe;IATpD,OAAO,CAAC,2BAA2B,CAAsB;IACzD,OAAO,CAAC,iBAAiB,CAA4B;IAErD,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAqB;IAEhE;;;OAGG;gBACyB,OAAO,EAAE,eAAe;IAMpD;;;OAGG;IACH,QAAQ;IAgBR;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAoB1B;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAU3B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAwBzB;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAerB;;;;;OAKG;IACH,OAAO,CAAC,kBAAkB;IAuD1B;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IA6DzB;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAgC/B;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAqEzB;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IA8CxB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IA4CvB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,cAAc;CAUvB"}
|
|
@@ -26,12 +26,7 @@ export declare class CommandClientGenerator implements Generator {
|
|
|
26
26
|
processCommandEndpointPaths(clientFile: SourceFile, aggregateDefinition: AggregateDefinition): void;
|
|
27
27
|
getEndpointPath(command: CommandDefinition): string;
|
|
28
28
|
processCommandClient(clientFile: SourceFile, aggregateDefinition: AggregateDefinition, isStream?: boolean): void;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* @param sourceFile - The source file containing the client
|
|
32
|
-
* @param client - The client class declaration
|
|
33
|
-
* @param definition - The command definition
|
|
34
|
-
*/
|
|
35
|
-
processCommandMethod(sourceFile: SourceFile, client: ClassDeclaration, definition: CommandDefinition, returnType: string): void;
|
|
29
|
+
private resolveParameters;
|
|
30
|
+
processCommandMethod(aggregate: AggregateDefinition, sourceFile: SourceFile, client: ClassDeclaration, definition: CommandDefinition, returnType: string): void;
|
|
36
31
|
}
|
|
37
32
|
//# sourceMappingURL=commandClientGenerator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commandClientGenerator.d.ts","sourceRoot":"","sources":["../../src/client/commandClientGenerator.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EACL,gBAAgB,EAChB,UAAU,EAEX,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAiBtE;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,SAAS;aAS1B,OAAO,EAAE,eAAe;IARpD,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAA4B;IACrE,OAAO,CAAC,QAAQ,CAAC,+BAA+B,CACb;IAEnC;;;OAGG;gBACyB,OAAO,EAAE,eAAe;IAGpD;;OAEG;IACH,QAAQ,IAAI,IAAI;IAwBhB;;;OAGG;IACH,gBAAgB,CAAC,SAAS,EAAE,mBAAmB;IA6E/C,2BAA2B,CACzB,UAAU,EAAE,UAAU,EACtB,mBAAmB,EAAE,mBAAmB;IAsB1C,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM;IAInD,oBAAoB,CAClB,UAAU,EAAE,UAAU,EACtB,mBAAmB,EAAE,mBAAmB,EACxC,QAAQ,GAAE,OAAe;IAyB3B
|
|
1
|
+
{"version":3,"file":"commandClientGenerator.d.ts","sourceRoot":"","sources":["../../src/client/commandClientGenerator.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EACL,gBAAgB,EAChB,UAAU,EAEX,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAiBtE;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,SAAS;aAS1B,OAAO,EAAE,eAAe;IARpD,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAA4B;IACrE,OAAO,CAAC,QAAQ,CAAC,+BAA+B,CACb;IAEnC;;;OAGG;gBACyB,OAAO,EAAE,eAAe;IAGpD;;OAEG;IACH,QAAQ,IAAI,IAAI;IAwBhB;;;OAGG;IACH,gBAAgB,CAAC,SAAS,EAAE,mBAAmB;IA6E/C,2BAA2B,CACzB,UAAU,EAAE,UAAU,EACtB,mBAAmB,EAAE,mBAAmB;IAsB1C,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM;IAInD,oBAAoB,CAClB,UAAU,EAAE,UAAU,EACtB,mBAAmB,EAAE,mBAAmB,EACxC,QAAQ,GAAE,OAAe;IAyB3B,OAAO,CAAC,iBAAiB;IAiEzB,oBAAoB,CAClB,SAAS,EAAE,mBAAmB,EAC9B,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,iBAAiB,EAC7B,UAAU,EAAE,MAAM;CAmCrB"}
|
|
@@ -19,6 +19,7 @@ export declare class GenerateContext implements GenerateContextInit {
|
|
|
19
19
|
constructor(context: GenerateContextInit);
|
|
20
20
|
getOrCreateSourceFile(filePath: string): SourceFile;
|
|
21
21
|
isIgnoreApiClientPathParameters(tagName: string, parameterName: string): boolean;
|
|
22
|
+
isIgnoreCommandClientPathParameters(tagName: string, parameterName: string): boolean;
|
|
22
23
|
}
|
|
23
24
|
export interface Generator {
|
|
24
25
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateContext.d.ts","sourceRoot":"","sources":["../src/generateContext.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAGvD,qBAAa,eAAgB,YAAW,mBAAmB;IACzD,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,iBAAiB,EAAE,wBAAwB,CAAC;IACrD,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;IACxC,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAA2B;IACvE,QAAQ,CAAC,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAC;gBAErC,OAAO,EAAE,mBAAmB;IAUxC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU;IAInD,+BAA+B,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO;
|
|
1
|
+
{"version":3,"file":"generateContext.d.ts","sourceRoot":"","sources":["../src/generateContext.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAGvD,qBAAa,eAAgB,YAAW,mBAAmB;IACzD,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4DAA4D;IAC5D,QAAQ,CAAC,iBAAiB,EAAE,wBAAwB,CAAC;IACrD,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;IACxC,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAA2B;IACvE,QAAQ,CAAC,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAC;gBAErC,OAAO,EAAE,mBAAmB;IAUxC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU;IAInD,+BAA+B,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO;IAKhF,mCAAmC,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO;CAGrF;AAED,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,QAAQ,IAAI,IAAI,CAAC;CAClB"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("@ahoo-wang/fetcher"),
|
|
2
|
-
`):void 0}function A(
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("@ahoo-wang/fetcher"),w=require("ts-morph"),M=require("@ahoo-wang/fetcher-wow"),B=require("yaml"),ce=require("fs"),j=require("path");function ge(r){const e=r.split(".");return e.length!=2||e[0].length===0||e[1].length===0?null:e}function le(r){const e=ge(r.name);return e?{tag:r,contextAlias:e[0],aggregateName:e[1]}:null}function pe(r){const e=r?.map(n=>le(n)).filter(n=>n!==null);if(!e)return new Map;const t=new Map;return e.forEach(n=>{t.set(n.tag.name,{aggregate:n,commands:new Map,events:new Map})}),t}function ue(r){if(!r)return null;const e=r.split(".");return e.length!=3?null:e[2]}function T(r){return r.$ref.split("/").pop()}function R(r,e){const t=T(r);return e.schemas?.[t]}function k(r,e){const t=T(r);return e.requestBodies?.[t]}function L(r,e){const t=T(r);return e.parameters?.[t]}function I(r,e){return{key:T(r),schema:R(r,e)}}const G=/[-_\s.]+/;function Q(r){return Array.isArray(r)?r.flatMap(e=>_(e.split(G))):_(r.split(G))}function _(r){return r.flatMap(e=>{if(e.length===0)return[];const t=[];let n="";for(let o=0;o<e.length;o++){const s=e[o],i=/[A-Z]/.test(s),a=o>0&&/[a-z]/.test(e[o-1]);i&&a&&n?(t.push(n),n=s):n+=s}return n&&t.push(n),t})}function C(r){return r===""||r.length===0?"":Q(r).filter(t=>t.length>0).map(t=>{const n=t.charAt(0),o=t.slice(1);return(/[a-zA-Z]/.test(n)?n.toUpperCase():n)+o.toLowerCase()}).join("")}function y(r){const e=C(r);return e.charAt(0).toLowerCase()+e.slice(1)}function de(r){return r===""||Array.isArray(r)&&r.length===0?"":Q(r).filter(t=>t.length>0).map(t=>t.toUpperCase()).join("_")}function U(r){return r.startsWith("http://")||r.startsWith("https://")?me(r):fe(r)}async function me(r){return await(await fetch(r)).text()}function fe(r){return new Promise((e,t)=>{ce.readFile(r,"utf-8",(n,o)=>{n?t(n):e(o)})})}async function he(r){const e=await U(r);switch(V(e)){case"json":return JSON.parse(e);case"yaml":return B.parse(e);default:throw new Error(`Unsupported file format: ${r}`)}}async function ye(r){const e=await U(r);switch(V(e)){case"json":return JSON.parse(e);case"yaml":return B.parse(e);default:throw new Error(`Unsupported file format: ${r}`)}}function V(r){const e=r.trimStart();if(e.startsWith("{")||e.startsWith("["))return"json";if(e.startsWith("-")||e.startsWith("%YAML"))return"yaml";try{return JSON.parse(e),"json"}catch{if(e.length>0)return"yaml"}throw new Error("Unable to infer file format")}function l(r){return!!(r&&typeof r=="object"&&"$ref"in r)}function N(r,e){if(e&&!l(e)&&e.content)return e.content[r]?.schema}function J(r){return N(h.ContentTypeValues.APPLICATION_JSON,r)}function xe(r){return N(h.ContentTypeValues.TEXT_EVENT_STREAM,r)}function Ae(r){return N("*/*",r)}const Ce=["string","number","integer","boolean","null"];function D(r){return Array.isArray(r)?!0:Ce.includes(r)}function v(r){return r.type==="array"}function Pe(r){return Array.isArray(r.enum)&&r.enum.length>0}function K(r){return Array.isArray(r.anyOf)&&r.anyOf.length>0}function H(r){return Array.isArray(r.oneOf)&&r.oneOf.length>0}function $e(r){return K(r)||H(r)}function Te(r){return Array.isArray(r.allOf)&&r.allOf.length>0}function F(r){return K(r)||H(r)||Te(r)}function W(r){return r.includes("|")||r.includes("&")?`(${r})[]`:`${r}[]`}function Ie(r){return r.type!=="object"?!1:r.properties?Object.keys(r.properties).length===0:!0}function P(r){if(Array.isArray(r))return r.map(e=>P(e)).join(" | ");switch(r){case"string":return"string";case"number":case"integer":return"number";case"boolean":return"boolean";case"null":return"null";default:return"any"}}function Y(r){return[{method:"get",operation:r.get},{method:"put",operation:r.put},{method:"post",operation:r.post},{method:"delete",operation:r.delete},{method:"options",operation:r.options},{method:"head",operation:r.head},{method:"patch",operation:r.patch},{method:"trace",operation:r.trace}].filter(({operation:e})=>e!==void 0)}function b(r){return r.responses[200]}function z(r){const e=b(r);return J(e)}function Se(r,e){return r.parameters?r.parameters.map(t=>l(t)?L(t,e):t).filter(t=>t.in==="path"):[]}const we="string";function X(r){return!r.schema||l(r.schema)||!r.schema.type||!D(r.schema.type)?we:P(r.schema.type)}const Z="types.ts",Re="@";function Ee(r){return h.combineURLs(r.path,Z)}function ee(r,e,t){const n=h.combineURLs(e,t),o=r.getSourceFile(n);return o||r.createSourceFile(n,"",{overwrite:!0})}function E(r,e,t){let n=r.getImportDeclaration(o=>o.getModuleSpecifierValue()===e);n||(n=r.addImportDeclaration({moduleSpecifier:e})),t.forEach(o=>{n.getNamedImports().some(i=>i.getName()===o)||n.addNamedImport(o)})}function f(r,e,t){if(t.path.startsWith(Re)){E(r,t.path,[t.name]);return}const n=r.getDirectoryPath(),o=j.join(e,t.path,Z);let s=j.relative(n,o);s=s.replace(/\.ts$/,""),s.startsWith(".")||(s="./"+s),E(r,s,[t.name])}function S(r,e,t,n){r.path!==n.path&&f(e,t,n)}function ve(r,e){const t=[r,e].filter(n=>n!==void 0&&n.length>0);return t.length>0?t.join(`
|
|
2
|
+
`):void 0}function A(r,e,t){const n=ve(e,t);n&&r.addJsDoc({description:n})}const Oe="#/components/responses/wow.CommandOk",Ne="#/components/parameters/wow.id";class De{constructor(e){this.openAPI=e,this.aggregates=pe(e.tags),this.build()}aggregates;build(){for(const[e,t]of Object.entries(this.openAPI.paths)){const n=Y(t);for(const o of n)this.commands(e,o),this.state(o.operation),this.events(o.operation),this.fields(o.operation)}}resolve(){const e=new Map;for(const t of this.aggregates.values()){if(!t.state||!t.fields)continue;const n=t.aggregate.contextAlias;let o=e.get(n);o||(o=new Set,e.set(n,o)),o.add(t)}return e}commands(e,t){const n=t.operation;if(n.operationId==="wow.command.send")return;const o=ue(n.operationId);if(!o)return;const s=b(n);if(!s||!l(s)||s.$ref!==Oe||!n.requestBody)return;const i=n.parameters??[],a=i.filter(d=>l(d)&&d.$ref===Ne).at(0),c=i.filter(d=>!l(d)&&d.in==="path");if(a){const d=L(a,this.openAPI.components);c.push(d)}const u=n.requestBody.content[h.ContentTypeValues.APPLICATION_JSON].schema,p=I(u,this.openAPI.components);p.schema.title=p.schema.title||n.summary,p.schema.description=p.schema.description||n.description;const ae={name:o,method:t.method,path:e,pathParameters:c,summary:n.summary,description:n.description,schema:p,operation:n};n.tags?.forEach(d=>{const q=this.aggregates.get(d);q&&q.commands.set(o,ae)})}state(e){if(!e.operationId?.endsWith(".snapshot_state.single"))return;const t=z(e);if(!l(t))return;const n=I(t,this.openAPI.components);e.tags?.forEach(o=>{const s=this.aggregates.get(o);s&&(s.state=n)})}events(e){if(!this.openAPI.components||!e.operationId?.endsWith(".event.list_query"))return;const t=z(e);if(l(t))return;const n=t?.items;if(!l(n))return;const s=R(n,this.openAPI.components).properties.body.items.anyOf.map(i=>{const a=i.title,c=i.properties.name.const,g=i.properties.body,u=I(g,this.openAPI.components);return u.schema.title=u.schema.title||i.title,{title:a,name:c,schema:u}});e.tags?.forEach(i=>{const a=this.aggregates.get(i);a&&s.forEach(c=>{a.events.set(c.name,c)})})}fields(e){if(!this.openAPI.components||!e.operationId?.endsWith(".snapshot.count"))return;const n=k(e.requestBody,this.openAPI.components).content[h.ContentTypeValues.APPLICATION_JSON].schema,s=R(n,this.openAPI.components).properties?.field,i=I(s,this.openAPI.components);e.tags?.forEach(a=>{const c=this.aggregates.get(a);c&&(c.fields=i)})}}const $="@ahoo-wang/fetcher-wow",be={"wow.command.CommandResult":"CommandResult","wow.MessageHeaderSqlType":"MessageHeaderSqlType","wow.api.BindingError":"BindingError","wow.api.DefaultErrorInfo":"ErrorInfo","wow.api.RecoverableType":"RecoverableType","wow.api.command.DefaultDeleteAggregate":"DeleteAggregate","wow.api.command.DefaultRecoverAggregate":"RecoverAggregate","wow.api.messaging.FunctionInfoData":"FunctionInfo","wow.api.messaging.FunctionKind":"FunctionKind","wow.api.modeling.AggregateId":"AggregateId","wow.api.query.Condition":"Condition","wow.api.query.ConditionOptions":"ConditionOptions","wow.api.query.ListQuery":"ListQuery","wow.api.query.Operator":"Operator","wow.api.query.PagedQuery":"PagedQuery","wow.api.query.Pagination":"Pagination","wow.api.query.Projection":"Projection","wow.api.query.Sort":"FieldSort","wow.api.query.Sort.Direction":"SortDirection","wow.command.CommandStage":"CommandStage","wow.command.SimpleWaitSignal":"WaitSignal","wow.configuration.Aggregate":"Aggregate","wow.configuration.BoundedContext":"BoundedContext","wow.configuration.WowMetadata":"WowMetadata","wow.modeling.DomainEvent":"DomainEvent","wow.openapi.BatchResult":"BatchResult","wow.messaging.CompensationTarget":"CompensationTarget"};function m(r){if(!r)return{name:"",path:"/"};const e=be[r];if(e)return{name:e,path:$};const t=r.split(".");let n=-1;for(let c=0;c<t.length;c++)if(t[c]&&/^[A-Z]/.test(t[c])){n=c;break}if(n===-1)return{name:r,path:"/"};const o=t.slice(0,n),s=o.length>0?`/${o.join("/")}`:"/",i=t.slice(n);return{name:C(i),path:s}}function x(r){const e=T(r);return m(e)}class qe{constructor(e){this.context=e}getOrCreateSourceFile(e){const t=Ee(e);return this.context.getOrCreateSourceFile(t)}generate(){const e=this.context.openAPI.components?.schemas;if(!e){this.context.logger.info("No schemas found in OpenAPI specification");return}const t=this.stateAggregatedTypeNames(),n=this.filterSchemas(e,t);this.context.logger.progress(`Generating models for ${n.length} schemas`),n.forEach((o,s)=>{this.context.logger.progressWithCount(s+1,n.length,`Processing schema: ${o.key}`,2),this.generateKeyedSchema(o)}),this.context.logger.success("Model generation completed")}filterSchemas(e,t){return Object.entries(e).map(([n,o])=>({key:n,schema:o})).filter(n=>!this.isWowSchema(n.key,t))}isWowSchema(e,t){if(e.startsWith("wow.")||e.endsWith("AggregatedCondition")||e.endsWith("AggregatedDomainEventStream")||e.endsWith("AggregatedDomainEventStreamPagedList")||e.endsWith("AggregatedDomainEventStreamServerSentEventNonNullData")||e.endsWith("AggregatedListQuery")||e.endsWith("AggregatedPagedQuery")||e.endsWith("AggregatedSingleQuery"))return!0;const n=m(e);return t.has(n.name)}aggregatedSchemaSuffix=["MaterializedSnapshot","MaterializedSnapshotPagedList","MaterializedSnapshotServerSentEventNonNullData","PagedList","ServerSentEventNonNullData","Snapshot","StateEvent"];stateAggregatedTypeNames(){const e=new Set;for(const t of this.context.contextAggregates.values())for(const n of t)this.aggregatedSchemaSuffix.forEach(o=>{const s=m(n.state.key),i=C(s.name)+o;e.add(i)});return e}generateKeyedSchema({key:e,schema:t}){const n=m(e),o=this.getOrCreateSourceFile(n),s=this.process(n,o,t);s&&A(s,t.title,t.description)}process(e,t,n){if(Pe(n))return t.addEnum({name:e.name,isExported:!0,members:n.enum.filter(s=>typeof s=="string"&&s.length>0).map(s=>({name:de(s),initializer:`'${s}'`}))});if(v(n)&&l(n.items)){const s=x(n.items);return S(e,t,this.context.outputDir,s),t.addTypeAlias({name:e.name,type:W(s.name),isExported:!0})}const o=t.addInterface({name:e.name,isExported:!0});return n.type==="object"&&n.properties?this.processInterface(t,e,n,o):(F(n)&&(n.anyOf||n.oneOf||n.allOf).forEach(i=>{if(l(i)){const a=x(i);S(e,t,this.context.outputDir,a),o.addExtends(a.name);return}this.processInterface(t,e,i,o)}),o)}processObject(e,t,n){const o=e.addInterface({name:t.name,isExported:!0});return this.processInterface(e,t,n,o)}processInterface(e,t,n,o){for(const[s,i]of Object.entries(n.properties)){const a=this.resolvePropertyType(t,e,s,i);let c=o.getProperty(s);c?c.setType(a):c=o.addProperty({name:s,type:a}),l(i)||A(c,i.title,i.description)}return o}resolvePropertyType(e,t,n,o){if(l(o)){const s=x(o);return S(e,t,this.context.outputDir,s),s.name}if(o.const)return`'${o.const}'`;if(v(o)){const s=this.resolvePropertyType(e,t,n,o.items);return W(s)}if(o.type&&D(o.type))return P(o.type);if(F(o))return this.resolvePropertyCompositionType(e,t,o);if(o.type==="object"&&o.properties){const s={path:e.path,name:`${e.name}${C(n)}`},i=this.processObject(t,s,o);return A(i,o.title,o.description),s.name}return"any"}resolvePropertyCompositionType(e,t,n){const o=n.anyOf||n.oneOf||n.allOf,s=new Set;o.forEach(a=>{if(l(a)){const c=x(a);S(e,t,this.context.outputDir,c),s.add(c.name);return}s.add(P(a.type??"string"))});const i=$e(n)?"|":"&";return Array.from(s).join(i)}}function Me(r){let e=0,t=0;return r.commands.forEach(n=>{n.path.startsWith(M.ResourceAttributionPathSpec.TENANT)&&(e+=1),n.path.startsWith(M.ResourceAttributionPathSpec.OWNER)&&(t+=1)}),e===0&&t===0?"ResourceAttributionPathSpec.NONE":e>t?"ResourceAttributionPathSpec.TENANT":"ResourceAttributionPathSpec.OWNER"}function te(r,e,t,n){const o=`${t.contextAlias}/${t.aggregateName}/${n}.ts`;return ee(r,e,o)}function je(r,e){return`${C(r.aggregateName)}${e}`}function O(r){return r==="delete"?"del":r}class Ge{constructor(e){this.context=e}generate(){const e=Array.from(this.context.contextAggregates.values()).reduce((n,o)=>n+o.size,0);this.context.logger.info("--- Generating Query Clients ---"),this.context.logger.progress(`Generating query clients for ${e} aggregates`);let t=0;for(const[,n]of this.context.contextAggregates)n.forEach(o=>{t++,this.context.logger.progressWithCount(t,e,`Processing query client for aggregate: ${o.aggregate.aggregateName}`),this.processQueryClient(o)});this.context.logger.success("Query client generation completed")}createClientFilePath(e,t){return te(this.context.project,this.context.outputDir,e,t)}processQueryClient(e){const t=this.createClientFilePath(e.aggregate,"queryClient");this.context.logger.info(`Processing query client for aggregate: ${e.aggregate.aggregateName} in context: ${e.aggregate.contextAlias}`),this.context.logger.info(`Adding imports from ${$}: QueryClientFactory, QueryClientOptions, ResourceAttributionPathSpec`),t.addImportDeclaration({moduleSpecifier:$,namedImports:["QueryClientFactory","QueryClientOptions","ResourceAttributionPathSpec"]});const n="DEFAULT_QUERY_CLIENT_OPTIONS";this.context.logger.info(`Creating default query client options: ${n}`),t.addVariableStatement({declarationKind:w.VariableDeclarationKind.Const,declarations:[{name:n,type:"QueryClientOptions",initializer:`{
|
|
3
3
|
contextAlias: '${e.aggregate.contextAlias}',
|
|
4
4
|
aggregateName: '${e.aggregate.aggregateName}',
|
|
5
|
-
resourceAttribution: ${
|
|
6
|
-
}`}],isExported:!1});const
|
|
5
|
+
resourceAttribution: ${Me(e)},
|
|
6
|
+
}`}],isExported:!1});const o=[];this.context.logger.info(`Processing ${e.events.size} domain events for aggregate: ${e.aggregate.aggregateName}`);for(const u of e.events.values()){const p=m(u.schema.key);this.context.logger.info(`Adding import for event model: ${p.name} from path: ${p.path}`),f(t,this.context.outputDir,p),o.push(p)}const s="DOMAIN_EVENT_TYPES",i=o.map(u=>u.name).join(" | ");this.context.logger.info(`Creating domain event types union: ${s} = ${i}`),t.addTypeAlias({name:s,type:i});const a=`${y(e.aggregate.aggregateName)}QueryClientFactory`,c=m(e.state.key),g=m(e.fields.key);this.context.logger.info(`Adding import for state model: ${c.name} from path: ${c.path}`),f(t,this.context.outputDir,c),this.context.logger.info(`Adding import for fields model: ${g.name} from path: ${g.path}`),f(t,this.context.outputDir,g),this.context.logger.info(`Creating query client factory: ${a}`),t.addVariableStatement({declarationKind:w.VariableDeclarationKind.Const,declarations:[{name:a,initializer:`new QueryClientFactory<${c.name}, ${g.name} | string, ${s}>(${n})`}],isExported:!0}),this.context.logger.success(`Query client generation completed for aggregate: ${e.aggregate.aggregateName}`)}}const _e="@ahoo-wang/fetcher-decorator",Fe=["type ApiMetadata","type ApiMetadataCapable","api","post","put","patch","del","request","attribute","path","autoGeneratedError"],ne=`{
|
|
7
7
|
headers: { Accept: ContentTypeValues.TEXT_EVENT_STREAM },
|
|
8
8
|
resultExtractor: JsonEventStreamResultExtractor,
|
|
9
|
-
}`;function
|
|
9
|
+
}`;function oe(r){E(r,_e,Fe)}function re(r,e,t=[]){return e.addClass({name:r,isExported:!0,decorators:[{name:"api",arguments:t}]})}function se(r,e){r.addImplements("ApiMetadataCapable"),r.addConstructor({parameters:[{name:"apiMetadata",type:"ApiMetadata",hasQuestionToken:e===void 0,scope:w.Scope.Public,isReadonly:!0,initializer:e}]})}class We{constructor(e){this.context=e}commandEndpointPathsName="COMMAND_ENDPOINT_PATHS";defaultCommandClientOptionsName="DEFAULT_COMMAND_CLIENT_OPTIONS";generate(){const e=Array.from(this.context.contextAggregates.values()).reduce((n,o)=>n+o.size,0);this.context.logger.info("--- Generating Command Clients ---"),this.context.logger.progress(`Generating command clients for ${e} aggregates`);let t=0;for(const[,n]of this.context.contextAggregates)n.forEach(o=>{t++,this.context.logger.progressWithCount(t,e,`Processing command client for aggregate: ${o.aggregate.aggregateName}`),this.processAggregate(o)});this.context.logger.success("Command client generation completed")}processAggregate(e){this.context.logger.info(`Processing command client for aggregate: ${e.aggregate.aggregateName} in context: ${e.aggregate.contextAlias}`);const t=te(this.context.project,this.context.outputDir,e.aggregate,"commandClient");this.context.logger.info(`Processing command endpoint paths for ${e.commands.size} commands`),this.processCommandEndpointPaths(t,e),this.context.logger.info(`Creating default command client options: ${this.defaultCommandClientOptionsName}`),t.addVariableStatement({declarationKind:w.VariableDeclarationKind.Const,declarations:[{name:this.defaultCommandClientOptionsName,type:"ApiMetadata",initializer:`{
|
|
10
10
|
basePath: '${e.aggregate.contextAlias}'
|
|
11
|
-
}`}],isExported:!1}),this.context.logger.info(`Adding imports from ${$}: CommandRequest, CommandResult, CommandResultEventStream, DeleteAggregate, RecoverAggregate`),t.addImportDeclaration({moduleSpecifier:$,namedImports:["CommandRequest","CommandResult","CommandResultEventStream","DeleteAggregate","RecoverAggregate"],isTypeOnly:!0}),this.context.logger.info("Adding import from @ahoo-wang/fetcher-eventstream: JsonEventStreamResultExtractor"),t.addImportDeclaration({moduleSpecifier:"@ahoo-wang/fetcher-eventstream",namedImports:["JsonEventStreamResultExtractor"]}),this.context.logger.info("Adding import from @ahoo-wang/fetcher: ContentTypeValues"),E(t,"@ahoo-wang/fetcher",["ContentTypeValues"]),this.context.logger.info("Adding imports from @ahoo-wang/fetcher-decorator: ApiMetadata types and decorators"),te(t),this.context.logger.info("Generating standard command client class"),this.processCommandClient(t,e),this.context.logger.info("Generating stream command client class"),this.processCommandClient(t,e,!0),this.context.logger.success(`Command client generation completed for aggregate: ${e.aggregate.aggregateName}`)}processCommandEndpointPaths(e,t){this.context.logger.info(`Creating command endpoint paths enum: ${this.commandEndpointPathsName}`);const n=e.addEnum({name:this.commandEndpointPathsName});t.commands.forEach(r=>{this.context.logger.info(`Adding command endpoint: ${r.name.toUpperCase()} = '${r.path}'`),n.addMember({name:r.name.toUpperCase(),initializer:`'${r.path}'`})}),this.context.logger.success(`Command endpoint paths enum created with ${t.commands.size} entries`)}getEndpointPath(e){return`${this.commandEndpointPathsName}.${e.name.toUpperCase()}`}processCommandClient(e,t,n=!1){let r="CommandClient",s=[],i="Promise<CommandResult>";n&&(r="Stream"+r,s=["''",ee],i="Promise<CommandResultEventStream>");const a=be(t.aggregate,r),c=ne(a,e,s);oe(c,this.defaultCommandClientOptionsName),t.commands.forEach(g=>{this.processCommandMethod(e,c,g,i)})}processCommandMethod(e,t,n,r){const s=m(n.schema.key);this.context.logger.info(`Adding import for command model: ${s.name} from path: ${s.path}`),f(e,this.context.outputDir,s),this.context.logger.info(`Generating command method: ${y(n.name)} for command: ${n.name}`),this.context.logger.info(`Command method details: HTTP ${n.method}, path: ${n.path}, return type: ${r}`);const i=n.pathParameters.map(c=>(this.context.logger.info(`Adding path parameter: ${c.name} (type: string)`),{name:c.name,type:"string",hasQuestionToken:!1,decorators:[{name:"path",arguments:[`'${c.name}'`]}]}));this.context.logger.info(`Adding command request parameter: commandRequest (type: CommandRequest<${s.name}>)`),i.push({name:"commandRequest",hasQuestionToken:Te(n.schema.schema),type:`CommandRequest<${s.name}>`,decorators:[{name:"request",arguments:[]}]}),this.context.logger.info("Adding attributes parameter: attributes (type: Record<string, any>)"),i.push({name:"attributes",hasQuestionToken:!0,type:"Record<string, any>",decorators:[{name:"attribute",arguments:[]}]});const a=t.addMethod({name:y(n.name),decorators:[{name:O(n.method),arguments:[`${this.getEndpointPath(n)}`]}],parameters:i,returnType:r,statements:[`throw autoGeneratedError(${i.map(c=>c.name).join(",")});`]});(n.summary||n.description)&&this.context.logger.info(`Adding JSDoc documentation for method: ${y(n.name)}`),A(a,n.summary,n.description),this.context.logger.success(`Command method generated: ${y(n.name)}`)}}class Fe{constructor(e){this.context=e,this.apiMetadataCtorInitializer=this.context.currentContextAlias?`{basePath:'${this.context.currentContextAlias}'}`:void 0}defaultParameterRequestType="ParameterRequest";defaultReturnType={type:"Promise<any>"};apiMetadataCtorInitializer;generate(){this.context.logger.info("Starting API client generation");const e=this.resolveApiTags();this.context.logger.info(`Resolved ${e.size} API client tags: ${Array.from(e.keys()).join(", ")}`);const t=this.groupOperations(e);this.context.logger.info(`Grouped operations into ${t.size} tag groups`),this.generateApiClients(e,t),this.context.logger.success("API client generation completed")}generateApiClients(e,t){this.context.logger.info(`Generating ${t.size} API client classes`);let n=0;for(const[r,s]of t){n++,this.context.logger.progressWithCount(n,t.size,`Generating API client for tag: ${r}`);const i=e.get(r);this.generateApiClient(i,s)}}createApiClientFile(e){let t=e.path;return this.context.currentContextAlias&&(t=h.combineURLs(this.context.currentContextAlias,t)),t=h.combineURLs(t,`${e.name}ApiClient.ts`),this.context.logger.info(`Creating API client file: ${t}`),this.context.getOrCreateSourceFile(t)}generateApiClient(e,t){const n=m(e.name);this.context.logger.info(`Generating API client class: ${n.name}ApiClient with ${t.size} operations`);const r=this.createApiClientFile(n);te(r);const s=ne(n.name+"ApiClient",r);A(s,e.description),oe(s,this.apiMetadataCtorInitializer),this.context.logger.info(`Processing ${t.size} operations for ${n.name}ApiClient`),t.forEach(i=>{this.processOperation(e,r,s,i)}),this.context.logger.success(`Completed API client: ${n.name}ApiClient`)}getMethodName(e,t){const n=t.operationId.split(".");for(let r=n.length-1;r>=0;r--){const s=y(n.slice(r));if(!e.getMethod(s))return s}return y(n)}resolveRequestType(e,t){if(!t.requestBody)return this.context.logger.info(`No request body found for operation ${t.operationId}, using default: ${this.defaultParameterRequestType}`),this.defaultParameterRequestType;let n;if(l(t.requestBody)?(this.context.logger.info(`Extracting request body from reference for operation: ${t.operationId}`),n=B(t.requestBody,this.context.openAPI.components)):n=t.requestBody,!n)return this.context.logger.info(`Request body extraction failed for operation ${t.operationId}, using default: ${this.defaultParameterRequestType}`),this.defaultParameterRequestType;if(n.content["multipart/form-data"])return this.context.logger.info(`Detected multipart/form-data content for operation ${t.operationId}, using ParameterRequest<FormData>`),"ParameterRequest<FormData>";if(n.content["application/json"]){const r=n.content["application/json"].schema;if(l(r)){const s=x(r);this.context.logger.info(`Adding import for request body model: ${s.name} from ${s.path}`),f(e,this.context.outputDir,s);const i=`ParameterRequest<${s.name}>`;return this.context.logger.info(`Resolved request type for operation ${t.operationId}: ${i}`),i}}return this.context.logger.info(`Using default request type for operation ${t.operationId}: ${this.defaultParameterRequestType}`),this.defaultParameterRequestType}resolveParameters(e,t,n){if(!n.parameters)return this.context.logger.info(`No parameters found for operation ${n.operationId}`),[];const r=n.parameters?.filter(a=>!l(a)&&a.in==="path"&&!this.context.isIgnoreApiClientPathParameters(e.name,a.name))??[];this.context.logger.info(`Found ${r.length} path parameters for operation ${n.operationId}`);const s=r.map(a=>(this.context.logger.info(`Adding path parameter: ${a.name} (type: string)`),{name:a.name,type:"string",hasQuestionToken:!1,decorators:[{name:"path",arguments:[`'${a.name}'`]}]})),i=this.resolveRequestType(t,n);return this.context.logger.info(`Adding httpRequest parameter: ${i}`),s.push({name:"httpRequest",hasQuestionToken:i===this.defaultParameterRequestType,type:`${i}`,decorators:[{name:"request",arguments:[]}]}),this.context.logger.info("Adding attributes parameter: Record<string, any>"),s.push({name:"attributes",hasQuestionToken:!0,type:"Record<string, any>",decorators:[{name:"attribute",arguments:[]}]}),s}resolveSchemaReturnType(e,t){if(l(t)){const n=x(t);this.context.logger.info(`Adding import for response model: ${n.name} from ${n.path}`),f(e,this.context.outputDir,n);const r=`Promise<${n.name}>`;return this.context.logger.info(`Resolved reference return type: ${r}`),r}if(!t.type)return this.context.logger.info(`Schema has no type, using default return type: ${this.defaultReturnType.type}`),this.defaultReturnType.type;if(J(t.type)){const r=`Promise<${R(t.type)}>`;return this.context.logger.info(`Resolved primitive return type: ${r}`),r}return this.context.logger.info(`Using default return type: ${this.defaultReturnType.type}`),this.defaultReturnType.type}resolveReturnType(e,t){const n=D(t);if(!n)return this.context.logger.info(`No OK response found for operation ${t.operationId}, using default return type: ${this.defaultReturnType.type}`),this.defaultReturnType;const r=U(n);if(r){const a=this.resolveSchemaReturnType(e,r);return this.context.logger.info(`Resolved JSON response return type for operation ${t.operationId}: ${a}`),{stream:!1,type:a}}const s=ye(n);if(s){if(l(s)){const c=w(s,this.context.openAPI.components);if(v(c)&&l(c.items)){const g=x(c.items);this.context.logger.info(`Adding import for event stream model: ${g.name} from ${g.path}`),f(e,this.context.outputDir,g);const p=`Promise<JsonServerSentEventStream<${g.name.includes("ServerSentEvent")?`${g.name}['data']`:g.name}>>`;return this.context.logger.info(`Resolved event stream return type for operation ${t.operationId}: ${p}`),{stream:!0,type:p}}}const a="Promise<JsonServerSentEventStream<any>>";return this.context.logger.info(`Resolved generic event stream return type for operation ${t.operationId}: ${a}`),{stream:!0,type:a}}const i=xe(n);if(i){const a=this.resolveSchemaReturnType(e,i);return this.context.logger.info(`Resolved wildcard response return type for operation ${t.operationId}: ${a}`),{type:a}}return this.context.logger.info(`Using default return type for operation ${t.operationId}: ${this.defaultReturnType.type}`),this.defaultReturnType}processOperation(e,t,n,r){this.context.logger.info(`Processing operation: ${r.operation.operationId} (${r.method} ${r.path})`);const s=this.getMethodName(n,r.operation);this.context.logger.info(`Generated method name: ${s}`);const i=this.resolveParameters(e,t,r.operation),a=this.resolveReturnType(t,r.operation),c=a.stream?{name:O(r.method),arguments:[`'${r.path}'`,ee]}:{name:O(r.method),arguments:[`'${r.path}'`]};this.context.logger.info(`Creating method with ${i.length} parameters, return type: ${a.type}, stream: ${a.stream||!1}`);const g=n.addMethod({name:s,decorators:[c],parameters:i,returnType:a.type,statements:[`throw autoGeneratedError(${i.map(u=>u.name).join(",")});`]});A(g,r.operation.summary,r.operation.description),this.context.logger.success(`Operation method generated: ${s}`)}groupOperations(e){this.context.logger.info("Grouping operations by API client tags");const t=new Map;let n=0;for(const[r,s]of Object.entries(this.context.openAPI.paths)){const i=V(s).filter(a=>{if(!a.operation.operationId)return!1;const c=a.operation.tags;return!c||c.length==0?!1:c.every(g=>e.has(g))});this.context.logger.info(`Path ${r}: found ${i.length} valid operations`);for(const a of i)a.operation.tags.forEach(c=>{const g={...a,path:r};t.has(c)||t.set(c,new Set),t.get(c).add(g),n++})}return this.context.logger.info(`Grouped ${n} operations into ${t.size} tag groups`),t}resolveApiTags(){this.context.logger.info("Resolving API client tags from OpenAPI specification");const e=new Map,t=this.context.openAPI.tags?.length||0;let n=0;return this.context.openAPI.tags?.forEach(r=>{r.name!="wow"&&r.name!="Actuator"&&!this.isAggregateTag(r)?(e.set(r.name,r),n++,this.context.logger.info(`Included API client tag: ${r.name}`)):this.context.logger.info(`Excluded tag: ${r.name} (wow/Actuator/aggregate)`)}),this.context.logger.info(`Resolved ${n} API client tags from ${t} total tags`),e}isAggregateTag(e){for(const t of this.context.contextAggregates.values())for(const n of t)if(n.aggregate.tag.name===e.name)return!0;return!1}}class _e{constructor(e){this.context=e,this.queryClientGenerator=new qe(e),this.commandClientGenerator=new Ge(e),this.apiClientGenerator=new Fe(e)}queryClientGenerator;commandClientGenerator;apiClientGenerator;generate(){this.context.logger.info("--- Generating Clients ---"),this.context.logger.progress(`Generating clients for ${this.context.contextAggregates.size} bounded contexts`);let e=0;for(const[t]of this.context.contextAggregates)e++,this.context.logger.progressWithCount(e,this.context.contextAggregates.size,`Processing bounded context: ${t}`,1),this.processBoundedContext(t);this.queryClientGenerator.generate(),this.commandClientGenerator.generate(),this.apiClientGenerator.generate(),this.context.logger.success("Client generation completed")}processBoundedContext(e){const t=`${e}/boundedContext.ts`;this.context.logger.info(`Creating bounded context file: ${t}`);const n=this.context.getOrCreateSourceFile(t);this.context.logger.info(`Adding bounded context alias constant: BOUNDED_CONTEXT_ALIAS = '${e}'`),n.addStatements(`export const BOUNDED_CONTEXT_ALIAS = '${e}';`),this.context.logger.success(`Bounded context file created successfully: ${t}`)}}class We{project;openAPI;outputDir;contextAggregates;logger;config;defaultIgnorePathParameters=["tenantId","ownerId"];currentContextAlias;constructor(e){this.project=e.project,this.openAPI=e.openAPI,this.outputDir=e.outputDir,this.contextAggregates=e.contextAggregates,this.logger=e.logger,this.config=e.config??{},this.currentContextAlias=this.openAPI.info["x-wow-context-alias"]}getOrCreateSourceFile(e){return Y(this.project,this.outputDir,e)}isIgnoreApiClientPathParameters(e,t){return(this.config.apiClients?.[e]?.ignorePathParameters??this.defaultIgnorePathParameters).includes(t)}}const re="./fetcher-generator.config.json";class ze{constructor(e){this.options=e,this.project=e.project,this.options.logger.info("CodeGenerator instance created")}project;async generate(){this.options.logger.info("Starting code generation from OpenAPI specification"),this.options.logger.info(`Input path: ${this.options.inputPath}`),this.options.logger.info(`Output directory: ${this.options.outputDir}`),this.options.logger.info("Parsing OpenAPI specification");const e=await fe(this.options.inputPath);this.options.logger.info("OpenAPI specification parsed successfully"),this.options.logger.info("Resolving bounded context aggregates");const n=new ve(e).resolve();this.options.logger.info(`Resolved ${n.size} bounded context aggregates`);const r=this.options.configPath??re;let s={};try{this.options.logger.info("Parsing configuration file:",r),s=await he(r)}catch(g){this.options.logger.info("Configuration file parsing failed ",g)}const i=new We({openAPI:e,project:this.project,outputDir:this.options.outputDir,contextAggregates:n,logger:this.options.logger,config:s});this.options.logger.info("Generating models"),new Ne(i).generate(),this.options.logger.info("Models generated successfully"),this.options.logger.info("Generating clients"),new _e(i).generate(),this.options.logger.info("Clients generated successfully"),this.options.logger.info("Generating index files"),this.generateIndex(),this.options.logger.info("Index files generated successfully"),this.options.logger.info("Optimizing source files"),this.optimizeSourceFiles(),this.options.logger.info("Source files optimized successfully"),this.options.logger.info("Saving project to disk"),await this.project.save(),this.options.logger.info("Code generation completed successfully")}generateIndex(){this.options.logger.info(`Generating index files for output directory: ${this.options.outputDir}`);const e=this.project.getDirectory(this.options.outputDir);if(!e){this.options.logger.info("Output directory not found, skipping index generation");return}this.processDirectory(e),this.generateIndexForDirectory(e),this.options.logger.info("Index file generation completed")}processDirectory(e){const t=e.getDirectories();this.options.logger.info(`Processing ${t.length} subdirectories`);for(const n of t)this.options.logger.info(`Processing subdirectory: ${n.getPath()}`),this.generateIndexForDirectory(n),this.processDirectory(n)}generateIndexForDirectory(e){const t=e.getPath();this.options.logger.info(`Generating index for directory: ${t}`);const n=e.getSourceFiles().filter(a=>a.getBaseName().endsWith(".ts")&&a.getBaseName()!=="index.ts"),r=e.getDirectories();if(this.options.logger.info(`Found ${n.length} TypeScript files and ${r.length} subdirectories in ${t}`),n.length===0&&r.length===0){this.options.logger.info(`No files or subdirectories to export in ${t}, skipping index generation`);return}const s=`${t}/index.ts`;this.options.logger.info(`Creating/updating index file: ${s}`);const i=this.project.getSourceFile(s)||this.project.createSourceFile(s,"",{overwrite:!0});i.removeText();for(const a of n){const c=`./${a.getBaseNameWithoutExtension()}`;this.options.logger.info(`Adding export for file: ${c}`),i.addExportDeclaration({moduleSpecifier:c,isTypeOnly:!1,namedExports:[]})}for(const a of r){const c=`./${a.getBaseName()}`;this.options.logger.info(`Adding export for subdirectory: ${c}`),i.addExportDeclaration({moduleSpecifier:c,isTypeOnly:!1,namedExports:[]})}this.options.logger.info(`Index file generated for ${t} with ${n.length+r.length} exports`)}optimizeSourceFiles(){const e=this.project.getSourceFiles();this.options.logger.info(`Optimizing ${e.length} source files`),e.forEach((t,n)=>{this.options.logger.info(`Optimizing file ${n+1}/${e.length}`),t.formatText(),t.organizeImports(),t.fixMissingImports()}),this.options.logger.info("All source files optimized")}}exports.CodeGenerator=ze;exports.DEFAULT_CONFIG_PATH=re;
|
|
11
|
+
}`}],isExported:!1}),this.context.logger.info(`Adding imports from ${$}: CommandRequest, CommandResult, CommandResultEventStream, DeleteAggregate, RecoverAggregate`),t.addImportDeclaration({moduleSpecifier:$,namedImports:["CommandRequest","CommandResult","CommandResultEventStream","DeleteAggregate","RecoverAggregate"],isTypeOnly:!0}),this.context.logger.info("Adding import from @ahoo-wang/fetcher-eventstream: JsonEventStreamResultExtractor"),t.addImportDeclaration({moduleSpecifier:"@ahoo-wang/fetcher-eventstream",namedImports:["JsonEventStreamResultExtractor"]}),this.context.logger.info("Adding import from @ahoo-wang/fetcher: ContentTypeValues"),E(t,"@ahoo-wang/fetcher",["ContentTypeValues"]),this.context.logger.info("Adding imports from @ahoo-wang/fetcher-decorator: ApiMetadata types and decorators"),oe(t),this.context.logger.info("Generating standard command client class"),this.processCommandClient(t,e),this.context.logger.info("Generating stream command client class"),this.processCommandClient(t,e,!0),this.context.logger.success(`Command client generation completed for aggregate: ${e.aggregate.aggregateName}`)}processCommandEndpointPaths(e,t){this.context.logger.info(`Creating command endpoint paths enum: ${this.commandEndpointPathsName}`);const n=e.addEnum({name:this.commandEndpointPathsName});t.commands.forEach(o=>{this.context.logger.info(`Adding command endpoint: ${o.name.toUpperCase()} = '${o.path}'`),n.addMember({name:o.name.toUpperCase(),initializer:`'${o.path}'`})}),this.context.logger.success(`Command endpoint paths enum created with ${t.commands.size} entries`)}getEndpointPath(e){return`${this.commandEndpointPathsName}.${e.name.toUpperCase()}`}processCommandClient(e,t,n=!1){let o="CommandClient",s=[],i="Promise<CommandResult>";n&&(o="Stream"+o,s=["''",ne],i="Promise<CommandResultEventStream>");const a=je(t.aggregate,o),c=re(a,e,s);se(c,this.defaultCommandClientOptionsName),t.commands.forEach(g=>{this.processCommandMethod(t,e,c,g,i)})}resolveParameters(e,t,n){const o=m(n.schema.key);this.context.logger.info(`Adding import for command model: ${o.name} from path: ${o.path}`),f(t,this.context.outputDir,o);const s=n.pathParameters.filter(i=>!this.context.isIgnoreCommandClientPathParameters(e.name,i.name)).map(i=>{const a=X(i);return this.context.logger.info(`Adding path parameter: ${i.name} (type: ${a})`),{name:i.name,type:a,hasQuestionToken:!1,decorators:[{name:"path",arguments:[`'${i.name}'`]}]}});return this.context.logger.info(`Adding command request parameter: commandRequest (type: CommandRequest<${o.name}>)`),s.push({name:"commandRequest",hasQuestionToken:Ie(n.schema.schema),type:`CommandRequest<${o.name}>`,decorators:[{name:"request",arguments:[]}]}),this.context.logger.info("Adding attributes parameter: attributes (type: Record<string, any>)"),s.push({name:"attributes",hasQuestionToken:!0,type:"Record<string, any>",decorators:[{name:"attribute",arguments:[]}]}),s}processCommandMethod(e,t,n,o,s){this.context.logger.info(`Generating command method: ${y(o.name)} for command: ${o.name}`),this.context.logger.info(`Command method details: HTTP ${o.method}, path: ${o.path}, return type: ${s}`);const i=this.resolveParameters(e.aggregate.tag,t,o),a=n.addMethod({name:y(o.name),decorators:[{name:O(o.method),arguments:[`${this.getEndpointPath(o)}`]}],parameters:i,returnType:s,statements:[`throw autoGeneratedError(${i.map(c=>c.name).join(",")});`]});(o.summary||o.description)&&this.context.logger.info(`Adding JSDoc documentation for method: ${y(o.name)}`),A(a,o.summary,o.description),this.context.logger.success(`Command method generated: ${y(o.name)}`)}}class ze{constructor(e){this.context=e,this.apiMetadataCtorInitializer=this.context.currentContextAlias?`{basePath:'${this.context.currentContextAlias}'}`:void 0}defaultParameterRequestType="ParameterRequest";defaultReturnType={type:"Promise<any>"};apiMetadataCtorInitializer;generate(){this.context.logger.info("Starting API client generation");const e=this.resolveApiTags();this.context.logger.info(`Resolved ${e.size} API client tags: ${Array.from(e.keys()).join(", ")}`);const t=this.groupOperations(e);this.context.logger.info(`Grouped operations into ${t.size} tag groups`),this.generateApiClients(e,t),this.context.logger.success("API client generation completed")}generateApiClients(e,t){this.context.logger.info(`Generating ${t.size} API client classes`);let n=0;for(const[o,s]of t){n++,this.context.logger.progressWithCount(n,t.size,`Generating API client for tag: ${o}`);const i=e.get(o);this.generateApiClient(i,s)}}createApiClientFile(e){let t=e.path;return this.context.currentContextAlias&&(t=h.combineURLs(this.context.currentContextAlias,t)),t=h.combineURLs(t,`${e.name}ApiClient.ts`),this.context.logger.info(`Creating API client file: ${t}`),this.context.getOrCreateSourceFile(t)}generateApiClient(e,t){const n=m(e.name);this.context.logger.info(`Generating API client class: ${n.name}ApiClient with ${t.size} operations`);const o=this.createApiClientFile(n);oe(o);const s=re(n.name+"ApiClient",o);A(s,e.description),se(s,this.apiMetadataCtorInitializer),this.context.logger.info(`Processing ${t.size} operations for ${n.name}ApiClient`),t.forEach(i=>{this.processOperation(e,o,s,i)}),this.context.logger.success(`Completed API client: ${n.name}ApiClient`)}getMethodName(e,t){const n=t.operationId.split(".");for(let o=n.length-1;o>=0;o--){const s=y(n.slice(o));if(!e.getMethod(s))return s}return y(n)}resolveRequestType(e,t){if(!t.requestBody)return this.context.logger.info(`No request body found for operation ${t.operationId}, using default: ${this.defaultParameterRequestType}`),this.defaultParameterRequestType;let n;if(l(t.requestBody)?(this.context.logger.info(`Extracting request body from reference for operation: ${t.operationId}`),n=k(t.requestBody,this.context.openAPI.components)):n=t.requestBody,!n)return this.context.logger.info(`Request body extraction failed for operation ${t.operationId}, using default: ${this.defaultParameterRequestType}`),this.defaultParameterRequestType;if(n.content["multipart/form-data"])return this.context.logger.info(`Detected multipart/form-data content for operation ${t.operationId}, using ParameterRequest<FormData>`),"ParameterRequest<FormData>";if(n.content["application/json"]){const o=n.content["application/json"].schema;if(l(o)){const s=x(o);this.context.logger.info(`Adding import for request body model: ${s.name} from ${s.path}`),f(e,this.context.outputDir,s);const i=`ParameterRequest<${s.name}>`;return this.context.logger.info(`Resolved request type for operation ${t.operationId}: ${i}`),i}}return this.context.logger.info(`Using default request type for operation ${t.operationId}: ${this.defaultParameterRequestType}`),this.defaultParameterRequestType}resolveParameters(e,t,n){const o=Se(n,this.context.openAPI.components).filter(a=>!this.context.isIgnoreApiClientPathParameters(e.name,a.name));this.context.logger.info(`Found ${o.length} path parameters for operation ${n.operationId}`);const s=o.map(a=>{const c=X(a);return this.context.logger.info(`Adding path parameter: ${a.name} (type: ${c})`),{name:a.name,type:c,hasQuestionToken:!1,decorators:[{name:"path",arguments:[`'${a.name}'`]}]}}),i=this.resolveRequestType(t,n);return this.context.logger.info(`Adding httpRequest parameter: ${i}`),s.push({name:"httpRequest",hasQuestionToken:i===this.defaultParameterRequestType,type:`${i}`,decorators:[{name:"request",arguments:[]}]}),this.context.logger.info("Adding attributes parameter: Record<string, any>"),s.push({name:"attributes",hasQuestionToken:!0,type:"Record<string, any>",decorators:[{name:"attribute",arguments:[]}]}),s}resolveSchemaReturnType(e,t){if(l(t)){const n=x(t);this.context.logger.info(`Adding import for response model: ${n.name} from ${n.path}`),f(e,this.context.outputDir,n);const o=`Promise<${n.name}>`;return this.context.logger.info(`Resolved reference return type: ${o}`),o}if(!t.type)return this.context.logger.info(`Schema has no type, using default return type: ${this.defaultReturnType.type}`),this.defaultReturnType.type;if(D(t.type)){const o=`Promise<${P(t.type)}>`;return this.context.logger.info(`Resolved primitive return type: ${o}`),o}return this.context.logger.info(`Using default return type: ${this.defaultReturnType.type}`),this.defaultReturnType.type}resolveReturnType(e,t){const n=b(t);if(!n)return this.context.logger.info(`No OK response found for operation ${t.operationId}, using default return type: ${this.defaultReturnType.type}`),this.defaultReturnType;const o=J(n);if(o){const a=this.resolveSchemaReturnType(e,o);return this.context.logger.info(`Resolved JSON response return type for operation ${t.operationId}: ${a}`),{stream:!1,type:a}}const s=xe(n);if(s){if(l(s)){const c=R(s,this.context.openAPI.components);if(v(c)&&l(c.items)){const g=x(c.items);this.context.logger.info(`Adding import for event stream model: ${g.name} from ${g.path}`),f(e,this.context.outputDir,g);const p=`Promise<JsonServerSentEventStream<${g.name.includes("ServerSentEvent")?`${g.name}['data']`:g.name}>>`;return this.context.logger.info(`Resolved event stream return type for operation ${t.operationId}: ${p}`),{stream:!0,type:p}}}const a="Promise<JsonServerSentEventStream<any>>";return this.context.logger.info(`Resolved generic event stream return type for operation ${t.operationId}: ${a}`),{stream:!0,type:a}}const i=Ae(n);if(i){const a=this.resolveSchemaReturnType(e,i);return this.context.logger.info(`Resolved wildcard response return type for operation ${t.operationId}: ${a}`),{type:a}}return this.context.logger.info(`Using default return type for operation ${t.operationId}: ${this.defaultReturnType.type}`),this.defaultReturnType}processOperation(e,t,n,o){this.context.logger.info(`Processing operation: ${o.operation.operationId} (${o.method} ${o.path})`);const s=this.getMethodName(n,o.operation);this.context.logger.info(`Generated method name: ${s}`);const i=this.resolveParameters(e,t,o.operation),a=this.resolveReturnType(t,o.operation),c=a.stream?{name:O(o.method),arguments:[`'${o.path}'`,ne]}:{name:O(o.method),arguments:[`'${o.path}'`]};this.context.logger.info(`Creating method with ${i.length} parameters, return type: ${a.type}, stream: ${a.stream||!1}`);const g=n.addMethod({name:s,decorators:[c],parameters:i,returnType:a.type,statements:[`throw autoGeneratedError(${i.map(u=>u.name).join(",")});`]});A(g,o.operation.summary,o.operation.description),this.context.logger.success(`Operation method generated: ${s}`)}groupOperations(e){this.context.logger.info("Grouping operations by API client tags");const t=new Map;let n=0;for(const[o,s]of Object.entries(this.context.openAPI.paths)){const i=Y(s).filter(a=>{if(!a.operation.operationId)return!1;const c=a.operation.tags;return!c||c.length==0?!1:c.every(g=>e.has(g))});this.context.logger.info(`Path ${o}: found ${i.length} valid operations`);for(const a of i)a.operation.tags.forEach(c=>{const g={...a,path:o};t.has(c)||t.set(c,new Set),t.get(c).add(g),n++})}return this.context.logger.info(`Grouped ${n} operations into ${t.size} tag groups`),t}resolveApiTags(){this.context.logger.info("Resolving API client tags from OpenAPI specification");const e=new Map,t=this.context.openAPI.tags?.length||0;let n=0;return this.context.openAPI.tags?.forEach(o=>{o.name!="wow"&&o.name!="Actuator"&&!this.isAggregateTag(o)?(e.set(o.name,o),n++,this.context.logger.info(`Included API client tag: ${o.name}`)):this.context.logger.info(`Excluded tag: ${o.name} (wow/Actuator/aggregate)`)}),this.context.logger.info(`Resolved ${n} API client tags from ${t} total tags`),e}isAggregateTag(e){for(const t of this.context.contextAggregates.values())for(const n of t)if(n.aggregate.tag.name===e.name)return!0;return!1}}class Be{constructor(e){this.context=e,this.queryClientGenerator=new Ge(e),this.commandClientGenerator=new We(e),this.apiClientGenerator=new ze(e)}queryClientGenerator;commandClientGenerator;apiClientGenerator;generate(){this.context.logger.info("--- Generating Clients ---"),this.context.logger.progress(`Generating clients for ${this.context.contextAggregates.size} bounded contexts`);let e=0;for(const[t]of this.context.contextAggregates)e++,this.context.logger.progressWithCount(e,this.context.contextAggregates.size,`Processing bounded context: ${t}`,1),this.processBoundedContext(t);this.queryClientGenerator.generate(),this.commandClientGenerator.generate(),this.apiClientGenerator.generate(),this.context.logger.success("Client generation completed")}processBoundedContext(e){const t=`${e}/boundedContext.ts`;this.context.logger.info(`Creating bounded context file: ${t}`);const n=this.context.getOrCreateSourceFile(t);this.context.logger.info(`Adding bounded context alias constant: BOUNDED_CONTEXT_ALIAS = '${e}'`),n.addStatements(`export const BOUNDED_CONTEXT_ALIAS = '${e}';`),this.context.logger.success(`Bounded context file created successfully: ${t}`)}}class ke{project;openAPI;outputDir;contextAggregates;logger;config;defaultIgnorePathParameters=["tenantId","ownerId"];currentContextAlias;constructor(e){this.project=e.project,this.openAPI=e.openAPI,this.outputDir=e.outputDir,this.contextAggregates=e.contextAggregates,this.logger=e.logger,this.config=e.config??{},this.currentContextAlias=this.openAPI.info["x-wow-context-alias"]}getOrCreateSourceFile(e){return ee(this.project,this.outputDir,e)}isIgnoreApiClientPathParameters(e,t){return(this.config.apiClients?.[e]?.ignorePathParameters??this.defaultIgnorePathParameters).includes(t)}isIgnoreCommandClientPathParameters(e,t){return this.defaultIgnorePathParameters.includes(t)}}const ie="./fetcher-generator.config.json";class Le{constructor(e){this.options=e,this.project=e.project,this.options.logger.info("CodeGenerator instance created")}project;async generate(){this.options.logger.info("Starting code generation from OpenAPI specification"),this.options.logger.info(`Input path: ${this.options.inputPath}`),this.options.logger.info(`Output directory: ${this.options.outputDir}`),this.options.logger.info("Parsing OpenAPI specification");const e=await he(this.options.inputPath);this.options.logger.info("OpenAPI specification parsed successfully"),this.options.logger.info("Resolving bounded context aggregates");const n=new De(e).resolve();this.options.logger.info(`Resolved ${n.size} bounded context aggregates`);const o=this.options.configPath??ie;let s={};try{this.options.logger.info("Parsing configuration file:",o),s=await ye(o)}catch(g){this.options.logger.info("Configuration file parsing failed ",g)}const i=new ke({openAPI:e,project:this.project,outputDir:this.options.outputDir,contextAggregates:n,logger:this.options.logger,config:s});this.options.logger.info("Generating models"),new qe(i).generate(),this.options.logger.info("Models generated successfully"),this.options.logger.info("Generating clients"),new Be(i).generate(),this.options.logger.info("Clients generated successfully"),this.options.logger.info("Generating index files"),this.generateIndex(),this.options.logger.info("Index files generated successfully"),this.options.logger.info("Optimizing source files"),this.optimizeSourceFiles(),this.options.logger.info("Source files optimized successfully"),this.options.logger.info("Saving project to disk"),await this.project.save(),this.options.logger.info("Code generation completed successfully")}generateIndex(){this.options.logger.info(`Generating index files for output directory: ${this.options.outputDir}`);const e=this.project.getDirectory(this.options.outputDir);if(!e){this.options.logger.info("Output directory not found, skipping index generation");return}this.processDirectory(e),this.generateIndexForDirectory(e),this.options.logger.info("Index file generation completed")}processDirectory(e){const t=e.getDirectories();this.options.logger.info(`Processing ${t.length} subdirectories`);for(const n of t)this.options.logger.info(`Processing subdirectory: ${n.getPath()}`),this.generateIndexForDirectory(n),this.processDirectory(n)}generateIndexForDirectory(e){const t=e.getPath();this.options.logger.info(`Generating index for directory: ${t}`);const n=e.getSourceFiles().filter(a=>a.getBaseName().endsWith(".ts")&&a.getBaseName()!=="index.ts"),o=e.getDirectories();if(this.options.logger.info(`Found ${n.length} TypeScript files and ${o.length} subdirectories in ${t}`),n.length===0&&o.length===0){this.options.logger.info(`No files or subdirectories to export in ${t}, skipping index generation`);return}const s=`${t}/index.ts`;this.options.logger.info(`Creating/updating index file: ${s}`);const i=this.project.getSourceFile(s)||this.project.createSourceFile(s,"",{overwrite:!0});i.removeText();for(const a of n){const c=`./${a.getBaseNameWithoutExtension()}`;this.options.logger.info(`Adding export for file: ${c}`),i.addExportDeclaration({moduleSpecifier:c,isTypeOnly:!1,namedExports:[]})}for(const a of o){const c=`./${a.getBaseName()}`;this.options.logger.info(`Adding export for subdirectory: ${c}`),i.addExportDeclaration({moduleSpecifier:c,isTypeOnly:!1,namedExports:[]})}this.options.logger.info(`Index file generated for ${t} with ${n.length+o.length} exports`)}optimizeSourceFiles(){const e=this.project.getSourceFiles();this.options.logger.info(`Optimizing ${e.length} source files`),e.forEach((t,n)=>{this.options.logger.info(`Optimizing file ${n+1}/${e.length}`),t.formatText(),t.organizeImports(),t.fixMissingImports()}),this.options.logger.info("All source files optimized")}}exports.CodeGenerator=Le;exports.DEFAULT_CONFIG_PATH=ie;
|
|
12
12
|
//# sourceMappingURL=index.cjs.map
|