swallowkit 1.0.0-beta.2 → 1.0.0-beta.21
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/LICENSE +21 -21
- package/README.ja.md +312 -215
- package/README.md +369 -216
- package/dist/__tests__/fixtures.d.ts +22 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -0
- package/dist/__tests__/fixtures.js +146 -0
- package/dist/__tests__/fixtures.js.map +1 -0
- package/dist/cli/commands/add-auth.d.ts +10 -0
- package/dist/cli/commands/add-auth.d.ts.map +1 -0
- package/dist/cli/commands/add-auth.js +444 -0
- package/dist/cli/commands/add-auth.js.map +1 -0
- package/dist/cli/commands/add-connector.d.ts +20 -0
- package/dist/cli/commands/add-connector.d.ts.map +1 -0
- package/dist/cli/commands/add-connector.js +163 -0
- package/dist/cli/commands/add-connector.js.map +1 -0
- package/dist/cli/commands/create-model.d.ts +1 -4
- package/dist/cli/commands/create-model.d.ts.map +1 -1
- package/dist/cli/commands/create-model.js +21 -82
- package/dist/cli/commands/create-model.js.map +1 -1
- package/dist/cli/commands/dev-seeds.d.ts +35 -0
- package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
- package/dist/cli/commands/dev-seeds.js +292 -0
- package/dist/cli/commands/dev-seeds.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +19 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +476 -117
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/index.d.ts +1 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +3 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.d.ts +13 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2627 -1708
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +3 -0
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +617 -129
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.d.ts +5 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +164 -42
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +8 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +90 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/mock/connector-mock-server.d.ts +101 -0
- package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
- package/dist/core/mock/connector-mock-server.js +480 -0
- package/dist/core/mock/connector-mock-server.js.map +1 -0
- package/dist/core/mock/zod-mock-generator.d.ts +14 -0
- package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
- package/dist/core/mock/zod-mock-generator.js +163 -0
- package/dist/core/mock/zod-mock-generator.js.map +1 -0
- package/dist/core/operations/create-model.d.ts +15 -0
- package/dist/core/operations/create-model.d.ts.map +1 -0
- package/dist/core/operations/create-model.js +171 -0
- package/dist/core/operations/create-model.js.map +1 -0
- package/dist/core/operations/runtime.d.ts +32 -0
- package/dist/core/operations/runtime.d.ts.map +1 -0
- package/dist/core/operations/runtime.js +225 -0
- package/dist/core/operations/runtime.js.map +1 -0
- package/dist/core/operations/scaffold-machine.d.ts +16 -0
- package/dist/core/operations/scaffold-machine.d.ts.map +1 -0
- package/dist/core/operations/scaffold-machine.js +63 -0
- package/dist/core/operations/scaffold-machine.js.map +1 -0
- package/dist/core/project/manifest.d.ts +92 -0
- package/dist/core/project/manifest.d.ts.map +1 -0
- package/dist/core/project/manifest.js +321 -0
- package/dist/core/project/manifest.js.map +1 -0
- package/dist/core/project/validation.d.ts +20 -0
- package/dist/core/project/validation.d.ts.map +1 -0
- package/dist/core/project/validation.js +204 -0
- package/dist/core/project/validation.js.map +1 -0
- package/dist/core/scaffold/auth-generator.d.ts +38 -0
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
- package/dist/core/scaffold/auth-generator.js +1244 -0
- package/dist/core/scaffold/auth-generator.js.map +1 -0
- package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
- package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
- package/dist/core/scaffold/connector-functions-generator.js +1027 -0
- package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
- package/dist/core/scaffold/functions-generator.d.ts +7 -1
- package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
- package/dist/core/scaffold/functions-generator.js +920 -213
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +20 -1
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +329 -135
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
- package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
- package/dist/core/scaffold/nextjs-generator.js +314 -182
- package/dist/core/scaffold/nextjs-generator.js.map +1 -1
- package/dist/core/scaffold/openapi-generator.d.ts +3 -0
- package/dist/core/scaffold/openapi-generator.d.ts.map +1 -0
- package/dist/core/scaffold/openapi-generator.js +190 -0
- package/dist/core/scaffold/openapi-generator.js.map +1 -0
- package/dist/core/scaffold/ui-generator.d.ts +10 -4
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +768 -663
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/database/base-model.d.ts +3 -3
- package/dist/database/base-model.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/machine/contracts.d.ts +16 -0
- package/dist/machine/contracts.d.ts.map +1 -0
- package/dist/machine/contracts.js +3 -0
- package/dist/machine/contracts.js.map +1 -0
- package/dist/machine/errors.d.ts +11 -0
- package/dist/machine/errors.d.ts.map +1 -0
- package/dist/machine/errors.js +34 -0
- package/dist/machine/errors.js.map +1 -0
- package/dist/machine/index.d.ts +3 -0
- package/dist/machine/index.d.ts.map +1 -0
- package/dist/machine/index.js +156 -0
- package/dist/machine/index.js.map +1 -0
- package/dist/mcp/index.d.ts +25 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +184 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/types/index.d.ts +65 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/package-manager.d.ts +109 -0
- package/dist/utils/package-manager.d.ts.map +1 -0
- package/dist/utils/package-manager.js +215 -0
- package/dist/utils/package-manager.js.map +1 -0
- package/package.json +85 -73
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +194 -0
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
- package/src/__tests__/auth.test.ts +654 -0
- package/src/__tests__/config.test.ts +263 -0
- package/src/__tests__/connector-functions-generator.test.ts +288 -0
- package/src/__tests__/connector-mock-server.test.ts +439 -0
- package/src/__tests__/connector-model-bff.test.ts +162 -0
- package/src/__tests__/dev-seeds.test.ts +112 -0
- package/src/__tests__/dev.test.ts +154 -0
- package/src/__tests__/fixtures.ts +144 -0
- package/src/__tests__/functions-generator.test.ts +237 -0
- package/src/__tests__/init.test.ts +80 -0
- package/src/__tests__/machine.test.ts +212 -0
- package/src/__tests__/mcp.test.ts +56 -0
- package/src/__tests__/model-parser.test.ts +72 -0
- package/src/__tests__/nextjs-generator.test.ts +97 -0
- package/src/__tests__/openapi-generator.test.ts +43 -0
- package/src/__tests__/package-manager.test.ts +189 -0
- package/src/__tests__/scaffold.test.ts +39 -0
- package/src/__tests__/string-utils.test.ts +75 -0
- package/src/__tests__/ui-generator.test.ts +144 -0
- package/src/__tests__/zod-mock-generator.test.ts +132 -0
- package/src/cli/commands/add-auth.ts +500 -0
- package/src/cli/commands/add-connector.ts +158 -0
- package/src/cli/commands/create-model.ts +62 -0
- package/src/cli/commands/dev-seeds.ts +358 -0
- package/src/cli/commands/dev.ts +962 -0
- package/src/cli/commands/index.ts +9 -0
- package/src/cli/commands/init.ts +3371 -0
- package/src/cli/commands/provision.ts +193 -0
- package/src/cli/commands/scaffold.ts +1211 -0
- package/src/cli/index.ts +193 -0
- package/src/core/config.ts +308 -0
- package/src/core/mock/connector-mock-server.ts +555 -0
- package/src/core/mock/zod-mock-generator.ts +205 -0
- package/src/core/operations/create-model.ts +174 -0
- package/src/core/operations/runtime.ts +235 -0
- package/src/core/operations/scaffold-machine.ts +91 -0
- package/src/core/project/manifest.ts +402 -0
- package/src/core/project/validation.ts +221 -0
- package/src/core/scaffold/auth-generator.ts +1284 -0
- package/src/core/scaffold/connector-functions-generator.ts +1128 -0
- package/src/core/scaffold/functions-generator.ts +970 -0
- package/src/core/scaffold/model-parser.ts +841 -0
- package/src/core/scaffold/nextjs-generator.ts +370 -0
- package/src/core/scaffold/openapi-generator.ts +212 -0
- package/src/core/scaffold/ui-generator.ts +1061 -0
- package/src/database/base-model.ts +184 -0
- package/src/database/client.ts +140 -0
- package/src/database/repository.ts +104 -0
- package/src/database/runtime-check.ts +25 -0
- package/src/index.ts +27 -0
- package/src/machine/contracts.ts +17 -0
- package/src/machine/errors.ts +34 -0
- package/src/machine/index.ts +173 -0
- package/src/mcp/index.ts +185 -0
- package/src/types/index.ts +134 -0
- package/src/utils/package-manager.ts +229 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js BFF API Routes コード生成
|
|
3
|
+
* Azure Functions を呼び出す BFF パターンの API routes を生成
|
|
4
|
+
* callFunction ヘルパー方式
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ModelInfo, toCamelCase } from "./model-parser";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* BFF callFunction ヘルパー (lib/api/call-function.ts) のコードを生成
|
|
11
|
+
*/
|
|
12
|
+
export function generateBFFCallFunction(): string {
|
|
13
|
+
return `import { NextRequest, NextResponse } from 'next/server';
|
|
14
|
+
import { z } from 'zod/v4';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* SwallowKit BFF Call Function Helper
|
|
18
|
+
* Azure Functions を呼び出す汎用ヘルパー
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // シンプルな GET
|
|
22
|
+
* return callFunction({ method: 'GET', path: '/api/todo', responseSchema: z.array(TodoSchema) });
|
|
23
|
+
*
|
|
24
|
+
* // バリデーション付き POST
|
|
25
|
+
* return callFunction({ method: 'POST', path: '/api/todo', body, inputSchema: InputSchema, responseSchema: TodoSchema, successStatus: 201 });
|
|
26
|
+
*
|
|
27
|
+
* // カスタムビジネスロジック関数の呼び出し
|
|
28
|
+
* return callFunction({ method: 'POST', path: '/api/todo/archive', body: { ids } });
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
function getFunctionsBaseUrl(): string {
|
|
32
|
+
return process.env.BACKEND_FUNCTIONS_BASE_URL || process.env.FUNCTIONS_BASE_URL || 'http://localhost:7071';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface CallFunctionConfig<TInput = any, TOutput = any> {
|
|
36
|
+
/** HTTP メソッド */
|
|
37
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
38
|
+
/** Azure Functions のパス (例: '/api/todo', '/api/todo/123') */
|
|
39
|
+
path: string;
|
|
40
|
+
/** リクエストボディ (POST/PUT 用) */
|
|
41
|
+
body?: any;
|
|
42
|
+
/** 入力バリデーション用 Zod スキーマ (省略時はバリデーションなし) */
|
|
43
|
+
inputSchema?: z.ZodSchema<TInput>;
|
|
44
|
+
/** 出力バリデーション用 Zod スキーマ (省略時はそのまま返す) */
|
|
45
|
+
responseSchema?: z.ZodSchema<TOutput>;
|
|
46
|
+
/** 成功時の HTTP ステータスコード (デフォルト: 200) */
|
|
47
|
+
successStatus?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function callFunction<TInput = any, TOutput = any>(
|
|
51
|
+
config: CallFunctionConfig<TInput, TOutput>
|
|
52
|
+
): Promise<NextResponse> {
|
|
53
|
+
const { method, path, body, inputSchema, responseSchema, successStatus = 200 } = config;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
// 入力バリデーション
|
|
57
|
+
let validatedBody = body;
|
|
58
|
+
if (inputSchema && body !== undefined) {
|
|
59
|
+
const result = inputSchema.safeParse(body);
|
|
60
|
+
if (!result.success) {
|
|
61
|
+
console.error('[BFF] Validation failed:', result.error.issues);
|
|
62
|
+
return NextResponse.json(
|
|
63
|
+
{ error: 'Validation failed', details: result.error.issues },
|
|
64
|
+
{ status: 400 }
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
validatedBody = result.data;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Azure Functions を呼び出し
|
|
71
|
+
const functionsBaseUrl = getFunctionsBaseUrl();
|
|
72
|
+
const url = functionsBaseUrl + path;
|
|
73
|
+
console.log(\`[BFF] \${method} \${url}\`);
|
|
74
|
+
|
|
75
|
+
const response = await fetch(url, {
|
|
76
|
+
method,
|
|
77
|
+
headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
body: validatedBody !== undefined ? JSON.stringify(validatedBody) : undefined,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
console.log('[BFF] Functions response status:', response.status);
|
|
82
|
+
|
|
83
|
+
// エラーレスポンスの転送
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
const text = await response.text();
|
|
86
|
+
console.error('[BFF] Functions error:', { status: response.status, body: text });
|
|
87
|
+
|
|
88
|
+
let errorMessage = 'Request failed';
|
|
89
|
+
try {
|
|
90
|
+
const error = JSON.parse(text);
|
|
91
|
+
errorMessage = error.error || errorMessage;
|
|
92
|
+
} catch {
|
|
93
|
+
errorMessage = text || errorMessage;
|
|
94
|
+
}
|
|
95
|
+
return NextResponse.json({ error: errorMessage }, { status: response.status });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// DELETE 204 の場合はボディなし
|
|
99
|
+
if (response.status === 204 || method === 'DELETE') {
|
|
100
|
+
return new NextResponse(null, { status: 204 });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// レスポンスの取得と出力バリデーション
|
|
104
|
+
const data = await response.json();
|
|
105
|
+
|
|
106
|
+
if (responseSchema) {
|
|
107
|
+
const validated = responseSchema.parse(data);
|
|
108
|
+
return NextResponse.json(validated, { status: successStatus });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return NextResponse.json(data, { status: successStatus });
|
|
112
|
+
} catch (error: any) {
|
|
113
|
+
console.error(\`[BFF] Error:\`, error);
|
|
114
|
+
return NextResponse.json(
|
|
115
|
+
{ error: error.message || 'Internal server error' },
|
|
116
|
+
{ status: 500 }
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 明示的ハンドラー方式の BFF ルートファイルを生成
|
|
125
|
+
*/
|
|
126
|
+
export function generateCompactBFFRoutes(model: ModelInfo, sharedPackageName: string): {
|
|
127
|
+
listRoute: string;
|
|
128
|
+
detailRoute: string;
|
|
129
|
+
} {
|
|
130
|
+
const modelName = model.name;
|
|
131
|
+
const modelCamel = toCamelCase(modelName);
|
|
132
|
+
const schemaName = model.schemaName;
|
|
133
|
+
|
|
134
|
+
const listRoute = `import { NextRequest } from 'next/server';
|
|
135
|
+
import { callFunction } from '@/lib/api/call-function';
|
|
136
|
+
import { ${schemaName} } from '${sharedPackageName}';
|
|
137
|
+
import { z } from 'zod/v4';
|
|
138
|
+
|
|
139
|
+
const InputSchema = ${schemaName}.omit({ id: true, createdAt: true, updatedAt: true });
|
|
140
|
+
|
|
141
|
+
// GET /api/${modelCamel} - 一覧取得
|
|
142
|
+
export async function GET() {
|
|
143
|
+
return callFunction({
|
|
144
|
+
method: 'GET',
|
|
145
|
+
path: '/api/${modelCamel}',
|
|
146
|
+
responseSchema: z.array(${schemaName}),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// POST /api/${modelCamel} - 新規作成
|
|
151
|
+
export async function POST(request: NextRequest) {
|
|
152
|
+
const body = await request.json();
|
|
153
|
+
return callFunction({
|
|
154
|
+
method: 'POST',
|
|
155
|
+
path: '/api/${modelCamel}',
|
|
156
|
+
body,
|
|
157
|
+
inputSchema: InputSchema,
|
|
158
|
+
responseSchema: ${schemaName},
|
|
159
|
+
successStatus: 201,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
const detailRoute = `import { NextRequest } from 'next/server';
|
|
165
|
+
import { callFunction } from '@/lib/api/call-function';
|
|
166
|
+
import { ${schemaName} } from '${sharedPackageName}';
|
|
167
|
+
|
|
168
|
+
const InputSchema = ${schemaName}.omit({ id: true, createdAt: true, updatedAt: true });
|
|
169
|
+
|
|
170
|
+
// GET /api/${modelCamel}/{id} - 詳細取得
|
|
171
|
+
export async function GET(
|
|
172
|
+
_request: NextRequest,
|
|
173
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
174
|
+
) {
|
|
175
|
+
const { id } = await params;
|
|
176
|
+
return callFunction({
|
|
177
|
+
method: 'GET',
|
|
178
|
+
path: \`/api/${modelCamel}/\${id}\`,
|
|
179
|
+
responseSchema: ${schemaName},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// PUT /api/${modelCamel}/{id} - 更新
|
|
184
|
+
export async function PUT(
|
|
185
|
+
request: NextRequest,
|
|
186
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
187
|
+
) {
|
|
188
|
+
const { id } = await params;
|
|
189
|
+
const body = await request.json();
|
|
190
|
+
return callFunction({
|
|
191
|
+
method: 'PUT',
|
|
192
|
+
path: \`/api/${modelCamel}/\${id}\`,
|
|
193
|
+
body,
|
|
194
|
+
inputSchema: InputSchema,
|
|
195
|
+
responseSchema: ${schemaName},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// DELETE /api/${modelCamel}/{id} - 削除
|
|
200
|
+
export async function DELETE(
|
|
201
|
+
_request: NextRequest,
|
|
202
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
203
|
+
) {
|
|
204
|
+
const { id } = await params;
|
|
205
|
+
return callFunction({
|
|
206
|
+
method: 'DELETE',
|
|
207
|
+
path: \`/api/${modelCamel}/\${id}\`,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
`;
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
listRoute,
|
|
214
|
+
detailRoute,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* コネクタ用 BFF ルートファイルを生成(操作制限に対応)
|
|
220
|
+
* 指定された operations のみハンドラーを生成する
|
|
221
|
+
*/
|
|
222
|
+
export function generateConnectorBFFRoutes(
|
|
223
|
+
model: ModelInfo,
|
|
224
|
+
sharedPackageName: string,
|
|
225
|
+
operations: readonly string[]
|
|
226
|
+
): {
|
|
227
|
+
listRoute: string;
|
|
228
|
+
detailRoute: string;
|
|
229
|
+
} {
|
|
230
|
+
const modelName = model.name;
|
|
231
|
+
const modelCamel = toCamelCase(modelName);
|
|
232
|
+
const schemaName = model.schemaName;
|
|
233
|
+
const ops = new Set(operations);
|
|
234
|
+
|
|
235
|
+
const hasGetAll = ops.has("getAll");
|
|
236
|
+
const hasCreate = ops.has("create");
|
|
237
|
+
const hasGetById = ops.has("getById");
|
|
238
|
+
const hasUpdate = ops.has("update");
|
|
239
|
+
const hasDelete = ops.has("delete");
|
|
240
|
+
|
|
241
|
+
// --- List route ---
|
|
242
|
+
let listImports = `import { callFunction } from '@/lib/api/call-function';
|
|
243
|
+
import { ${schemaName} } from '${sharedPackageName}';
|
|
244
|
+
import { z } from 'zod/v4';`;
|
|
245
|
+
|
|
246
|
+
if (hasCreate) {
|
|
247
|
+
listImports = `import { NextRequest } from 'next/server';
|
|
248
|
+
${listImports}
|
|
249
|
+
|
|
250
|
+
const InputSchema = ${schemaName}.omit({ id: true, createdAt: true, updatedAt: true });`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let listHandlers = "";
|
|
254
|
+
if (hasGetAll) {
|
|
255
|
+
listHandlers += `
|
|
256
|
+
// GET /api/${modelCamel} - 一覧取得
|
|
257
|
+
export async function GET() {
|
|
258
|
+
return callFunction({
|
|
259
|
+
method: 'GET',
|
|
260
|
+
path: '/api/${modelCamel}',
|
|
261
|
+
responseSchema: z.array(${schemaName}),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (hasCreate) {
|
|
268
|
+
listHandlers += `
|
|
269
|
+
// POST /api/${modelCamel} - 新規作成
|
|
270
|
+
export async function POST(request: NextRequest) {
|
|
271
|
+
const body = await request.json();
|
|
272
|
+
return callFunction({
|
|
273
|
+
method: 'POST',
|
|
274
|
+
path: '/api/${modelCamel}',
|
|
275
|
+
body,
|
|
276
|
+
inputSchema: InputSchema,
|
|
277
|
+
responseSchema: ${schemaName},
|
|
278
|
+
successStatus: 201,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const listRoute = `${listImports}
|
|
285
|
+
${listHandlers}`;
|
|
286
|
+
|
|
287
|
+
// --- Detail route ---
|
|
288
|
+
const needDetailImport = hasGetById || hasUpdate || hasDelete;
|
|
289
|
+
let detailImports = "";
|
|
290
|
+
let detailHandlers = "";
|
|
291
|
+
|
|
292
|
+
if (needDetailImport) {
|
|
293
|
+
const needNextRequest = hasUpdate;
|
|
294
|
+
detailImports = needNextRequest
|
|
295
|
+
? `import { NextRequest } from 'next/server';
|
|
296
|
+
import { callFunction } from '@/lib/api/call-function';
|
|
297
|
+
import { ${schemaName} } from '${sharedPackageName}';`
|
|
298
|
+
: `import { NextRequest } from 'next/server';
|
|
299
|
+
import { callFunction } from '@/lib/api/call-function';
|
|
300
|
+
import { ${schemaName} } from '${sharedPackageName}';`;
|
|
301
|
+
|
|
302
|
+
if (hasUpdate) {
|
|
303
|
+
detailImports += `\n\nconst InputSchema = ${schemaName}.omit({ id: true, createdAt: true, updatedAt: true });`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (hasGetById) {
|
|
307
|
+
detailHandlers += `
|
|
308
|
+
// GET /api/${modelCamel}/{id} - 詳細取得
|
|
309
|
+
export async function GET(
|
|
310
|
+
_request: NextRequest,
|
|
311
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
312
|
+
) {
|
|
313
|
+
const { id } = await params;
|
|
314
|
+
return callFunction({
|
|
315
|
+
method: 'GET',
|
|
316
|
+
path: \`/api/${modelCamel}/\${id}\`,
|
|
317
|
+
responseSchema: ${schemaName},
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (hasUpdate) {
|
|
324
|
+
detailHandlers += `
|
|
325
|
+
// PUT /api/${modelCamel}/{id} - 更新
|
|
326
|
+
export async function PUT(
|
|
327
|
+
request: NextRequest,
|
|
328
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
329
|
+
) {
|
|
330
|
+
const { id } = await params;
|
|
331
|
+
const body = await request.json();
|
|
332
|
+
return callFunction({
|
|
333
|
+
method: 'PUT',
|
|
334
|
+
path: \`/api/${modelCamel}/\${id}\`,
|
|
335
|
+
body,
|
|
336
|
+
inputSchema: InputSchema,
|
|
337
|
+
responseSchema: ${schemaName},
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (hasDelete) {
|
|
344
|
+
detailHandlers += `
|
|
345
|
+
// DELETE /api/${modelCamel}/{id} - 削除
|
|
346
|
+
export async function DELETE(
|
|
347
|
+
_request: NextRequest,
|
|
348
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
349
|
+
) {
|
|
350
|
+
const { id } = await params;
|
|
351
|
+
return callFunction({
|
|
352
|
+
method: 'DELETE',
|
|
353
|
+
path: \`/api/${modelCamel}/\${id}\`,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
`;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const detailRoute = needDetailImport
|
|
361
|
+
? `${detailImports}
|
|
362
|
+
${detailHandlers}`
|
|
363
|
+
: `// No detail operations defined for this connector model
|
|
364
|
+
`;
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
listRoute,
|
|
368
|
+
detailRoute,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { FieldInfo, ModelInfo, toCamelCase } from "./model-parser";
|
|
2
|
+
|
|
3
|
+
type OpenApiSchema =
|
|
4
|
+
| {
|
|
5
|
+
type?: string;
|
|
6
|
+
format?: string;
|
|
7
|
+
enum?: string[];
|
|
8
|
+
items?: OpenApiSchema;
|
|
9
|
+
properties?: Record<string, OpenApiSchema>;
|
|
10
|
+
required?: string[];
|
|
11
|
+
additionalProperties?: boolean;
|
|
12
|
+
nullable?: boolean;
|
|
13
|
+
$ref?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function toScalarSchema(field: FieldInfo): OpenApiSchema {
|
|
17
|
+
if (field.enumValues?.length) {
|
|
18
|
+
return {
|
|
19
|
+
type: "string",
|
|
20
|
+
enum: field.enumValues,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
switch (field.type) {
|
|
25
|
+
case "string":
|
|
26
|
+
return {
|
|
27
|
+
type: "string",
|
|
28
|
+
format: field.name.toLowerCase().endsWith("at") ? "date-time" : undefined,
|
|
29
|
+
};
|
|
30
|
+
case "date":
|
|
31
|
+
return {
|
|
32
|
+
type: "string",
|
|
33
|
+
format: "date-time",
|
|
34
|
+
};
|
|
35
|
+
case "number":
|
|
36
|
+
return {
|
|
37
|
+
type: "number",
|
|
38
|
+
};
|
|
39
|
+
case "boolean":
|
|
40
|
+
return {
|
|
41
|
+
type: "boolean",
|
|
42
|
+
};
|
|
43
|
+
default:
|
|
44
|
+
return {
|
|
45
|
+
type: "object",
|
|
46
|
+
additionalProperties: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function toFieldSchema(field: FieldInfo): OpenApiSchema {
|
|
52
|
+
const schema = field.isNestedSchema && field.nestedModelName
|
|
53
|
+
? {
|
|
54
|
+
$ref: `#/components/schemas/${field.nestedModelName}`,
|
|
55
|
+
}
|
|
56
|
+
: toScalarSchema(field);
|
|
57
|
+
|
|
58
|
+
if (field.isArray) {
|
|
59
|
+
return {
|
|
60
|
+
type: "array",
|
|
61
|
+
items: schema,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return schema;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createComponentSchema(model: ModelInfo): OpenApiSchema {
|
|
69
|
+
const properties: Record<string, OpenApiSchema> = {};
|
|
70
|
+
const required: string[] = [];
|
|
71
|
+
|
|
72
|
+
for (const field of model.fields) {
|
|
73
|
+
properties[field.name] = toFieldSchema(field);
|
|
74
|
+
if (!field.isOptional) {
|
|
75
|
+
required.push(field.name);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties,
|
|
82
|
+
required,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function generateOpenApiDocument(models: ModelInfo[], rootModel: ModelInfo): string {
|
|
87
|
+
const rootModelRoute = toCamelCase(rootModel.name);
|
|
88
|
+
const components = Object.fromEntries(
|
|
89
|
+
models.map((model) => [model.name, createComponentSchema(model)])
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const document = {
|
|
93
|
+
openapi: "3.0.3",
|
|
94
|
+
info: {
|
|
95
|
+
title: `${rootModel.name} API`,
|
|
96
|
+
version: "1.0.0",
|
|
97
|
+
description: "Generated from SwallowKit Zod model metadata.",
|
|
98
|
+
},
|
|
99
|
+
paths: {
|
|
100
|
+
[`/api/${rootModelRoute}`]: {
|
|
101
|
+
get: {
|
|
102
|
+
operationId: `${rootModelRoute}GetAll`,
|
|
103
|
+
responses: {
|
|
104
|
+
"200": {
|
|
105
|
+
description: "Successful response",
|
|
106
|
+
content: {
|
|
107
|
+
"application/json": {
|
|
108
|
+
schema: {
|
|
109
|
+
type: "array",
|
|
110
|
+
items: {
|
|
111
|
+
$ref: `#/components/schemas/${rootModel.name}`,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
post: {
|
|
120
|
+
operationId: `${rootModelRoute}Create`,
|
|
121
|
+
requestBody: {
|
|
122
|
+
required: true,
|
|
123
|
+
content: {
|
|
124
|
+
"application/json": {
|
|
125
|
+
schema: {
|
|
126
|
+
$ref: `#/components/schemas/${rootModel.name}`,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
responses: {
|
|
132
|
+
"201": {
|
|
133
|
+
description: "Created response",
|
|
134
|
+
content: {
|
|
135
|
+
"application/json": {
|
|
136
|
+
schema: {
|
|
137
|
+
$ref: `#/components/schemas/${rootModel.name}`,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
[`/api/${rootModelRoute}/{id}`]: {
|
|
146
|
+
parameters: [
|
|
147
|
+
{
|
|
148
|
+
name: "id",
|
|
149
|
+
in: "path",
|
|
150
|
+
required: true,
|
|
151
|
+
schema: {
|
|
152
|
+
type: "string",
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
get: {
|
|
157
|
+
operationId: `${rootModelRoute}GetById`,
|
|
158
|
+
responses: {
|
|
159
|
+
"200": {
|
|
160
|
+
description: "Successful response",
|
|
161
|
+
content: {
|
|
162
|
+
"application/json": {
|
|
163
|
+
schema: {
|
|
164
|
+
$ref: `#/components/schemas/${rootModel.name}`,
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
put: {
|
|
172
|
+
operationId: `${rootModelRoute}Update`,
|
|
173
|
+
requestBody: {
|
|
174
|
+
required: true,
|
|
175
|
+
content: {
|
|
176
|
+
"application/json": {
|
|
177
|
+
schema: {
|
|
178
|
+
$ref: `#/components/schemas/${rootModel.name}`,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
responses: {
|
|
184
|
+
"200": {
|
|
185
|
+
description: "Updated response",
|
|
186
|
+
content: {
|
|
187
|
+
"application/json": {
|
|
188
|
+
schema: {
|
|
189
|
+
$ref: `#/components/schemas/${rootModel.name}`,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
delete: {
|
|
197
|
+
operationId: `${rootModelRoute}Delete`,
|
|
198
|
+
responses: {
|
|
199
|
+
"204": {
|
|
200
|
+
description: "Deleted response",
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
components: {
|
|
207
|
+
schemas: components,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
return JSON.stringify(document, null, 2);
|
|
212
|
+
}
|