swallowkit 1.0.0-beta.11 → 1.0.0-beta.13
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.ja.md +33 -9
- package/README.md +33 -9
- package/dist/__tests__/fixtures.d.ts +8 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -1
- package/dist/__tests__/fixtures.js +60 -0
- package/dist/__tests__/fixtures.js.map +1 -1
- 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 +161 -0
- package/dist/cli/commands/add-connector.js.map +1 -0
- package/dist/cli/commands/create-model.d.ts +1 -0
- package/dist/cli/commands/create-model.d.ts.map +1 -1
- package/dist/cli/commands/create-model.js +65 -1
- package/dist/cli/commands/create-model.js.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +49 -3
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +130 -7
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/index.js +15 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +3 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +37 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/mock/connector-mock-server.d.ts +67 -0
- package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
- package/dist/core/mock/connector-mock-server.js +285 -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/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 +1009 -0
- package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
- package/dist/core/scaffold/model-parser.d.ts +6 -0
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +150 -28
- 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 +133 -0
- package/dist/core/scaffold/nextjs-generator.js.map +1 -1
- package/dist/types/index.d.ts +31 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/config.test.ts +141 -0
- package/src/__tests__/connector-functions-generator.test.ts +288 -0
- package/src/__tests__/connector-mock-server.test.ts +252 -0
- package/src/__tests__/connector-model-bff.test.ts +162 -0
- package/src/__tests__/fixtures.ts +60 -0
- package/src/__tests__/scaffold.test.ts +2 -2
- package/src/__tests__/zod-mock-generator.test.ts +132 -0
- package/src/cli/commands/add-connector.ts +157 -0
- package/src/cli/commands/create-model.ts +72 -2
- package/src/cli/commands/dev.ts +55 -4
- package/src/cli/commands/scaffold.ts +211 -12
- package/src/cli/index.ts +16 -0
- package/src/core/config.ts +42 -1
- package/src/core/mock/connector-mock-server.ts +307 -0
- package/src/core/mock/zod-mock-generator.ts +205 -0
- package/src/core/scaffold/connector-functions-generator.ts +1106 -0
- package/src/core/scaffold/model-parser.ts +172 -31
- package/src/core/scaffold/nextjs-generator.ts +154 -0
- package/src/types/index.ts +47 -0
|
@@ -6,6 +6,8 @@ import * as fs from "fs";
|
|
|
6
6
|
import * as path from "path";
|
|
7
7
|
import { pathToFileURL } from "url";
|
|
8
8
|
|
|
9
|
+
import { ModelConnectorConfig } from "../../types";
|
|
10
|
+
|
|
9
11
|
export interface ModelInfo {
|
|
10
12
|
name: string; // モデル名(例: "Todo")
|
|
11
13
|
displayName: string; // 表示名(例: "Todo" または "タスク")
|
|
@@ -16,6 +18,7 @@ export interface ModelInfo {
|
|
|
16
18
|
hasCreatedAt: boolean; // createdAt フィールドがあるか
|
|
17
19
|
hasUpdatedAt: boolean; // updatedAt フィールドがあるか
|
|
18
20
|
nestedSchemaRefs: NestedSchemaRef[]; // ネストしたスキーマ参照
|
|
21
|
+
connectorConfig?: ModelConnectorConfig; // コネクタメタデータ(外部データソース用)
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
export interface FieldInfo {
|
|
@@ -60,11 +63,15 @@ export async function parseModelFile(modelPath: string): Promise<ModelInfo> {
|
|
|
60
63
|
const modelName = toPascalCase(fileName);
|
|
61
64
|
|
|
62
65
|
// スキーマ変数名を抽出
|
|
66
|
+
// コメント内のコードパターンを誤認識しないよう、先にコメントを除去してから正規表現を適用する
|
|
63
67
|
// パターン1: export const todoSchema = z.object({ ... }) (camelCase + Schema接尾辞)
|
|
64
68
|
// パターン2: export const Todo = z.object({ ... }) (Zod公式パターン)
|
|
65
|
-
|
|
69
|
+
const contentWithoutComments = content
|
|
70
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // ブロックコメントを除去
|
|
71
|
+
.replace(/\/\/.*/g, ''); // 行コメントを除去
|
|
72
|
+
let schemaMatch = contentWithoutComments.match(/export\s+const\s+(\w+Schema)\s*=/);
|
|
66
73
|
if (!schemaMatch) {
|
|
67
|
-
schemaMatch =
|
|
74
|
+
schemaMatch = contentWithoutComments.match(/export\s+const\s+(\w+)\s*=\s*z\.object\s*\(/);
|
|
68
75
|
}
|
|
69
76
|
if (!schemaMatch) {
|
|
70
77
|
throw new Error(`Could not find exported schema in ${modelPath}. Expected patterns:\n - export const xxxSchema = z.object({ ... })\n - export const Xxx = z.object({ ... })`);
|
|
@@ -75,6 +82,9 @@ export async function parseModelFile(modelPath: string): Promise<ModelInfo> {
|
|
|
75
82
|
// displayName を抽出(例: export const displayName = 'Task')
|
|
76
83
|
const displayNameMatch = content.match(/export\s+const\s+displayName\s*=\s*['"]([^'"]+)['"]/);
|
|
77
84
|
const displayName = displayNameMatch ? displayNameMatch[1] : modelName;
|
|
85
|
+
|
|
86
|
+
// connectorConfig を抽出(外部データソース用メタデータ)
|
|
87
|
+
const connectorConfig = parseConnectorConfig(content);
|
|
78
88
|
|
|
79
89
|
// ネストしたスキーマ参照を検出
|
|
80
90
|
const nestedSchemaRefs = detectNestedSchemaRefs(modelPath, content, schemaName);
|
|
@@ -100,6 +110,7 @@ export async function parseModelFile(modelPath: string): Promise<ModelInfo> {
|
|
|
100
110
|
hasCreatedAt,
|
|
101
111
|
hasUpdatedAt,
|
|
102
112
|
nestedSchemaRefs,
|
|
113
|
+
...(connectorConfig ? { connectorConfig } : {}),
|
|
103
114
|
};
|
|
104
115
|
}
|
|
105
116
|
|
|
@@ -291,6 +302,59 @@ function mergeNestedSchemaInfo(fields: FieldInfo[], nestedRefs: NestedSchemaRef[
|
|
|
291
302
|
}
|
|
292
303
|
}
|
|
293
304
|
|
|
305
|
+
/**
|
|
306
|
+
* ローカル依存ファイルを再帰的にインライン化する(動的インポート用一時スクリプト生成用)。
|
|
307
|
+
* seen によって循環依存を防ぐ。
|
|
308
|
+
* importedNames が指定された場合はその名前の const だけを残し、他を除去する。
|
|
309
|
+
*/
|
|
310
|
+
function inlineLocalDeps(
|
|
311
|
+
filePath: string,
|
|
312
|
+
importedNames: Set<string> | null,
|
|
313
|
+
seen: Set<string>
|
|
314
|
+
): string {
|
|
315
|
+
const resolvedPath = path.resolve(filePath);
|
|
316
|
+
if (seen.has(resolvedPath)) return '';
|
|
317
|
+
seen.add(resolvedPath);
|
|
318
|
+
|
|
319
|
+
if (!fs.existsSync(resolvedPath)) return '';
|
|
320
|
+
|
|
321
|
+
let content = fs.readFileSync(resolvedPath, 'utf8');
|
|
322
|
+
const fileDir = path.dirname(resolvedPath);
|
|
323
|
+
const transitiveParts: string[] = [];
|
|
324
|
+
|
|
325
|
+
// ローカル(相対)インポートを再帰的にインライン化
|
|
326
|
+
content = content.replace(
|
|
327
|
+
/import\s*\{([^}]+)\}\s*from\s*['"](\.[^'"]+)['"]\s*;?/g,
|
|
328
|
+
(_match: string, _imports: string, importPath: string) => {
|
|
329
|
+
let depPath = path.resolve(fileDir, importPath);
|
|
330
|
+
if (!depPath.endsWith('.ts') && !depPath.endsWith('.js')) depPath += '.ts';
|
|
331
|
+
const inlined = inlineLocalDeps(depPath, null, seen);
|
|
332
|
+
if (inlined) transitiveParts.push(inlined);
|
|
333
|
+
return '';
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// 外部パッケージのインポートを除去
|
|
338
|
+
content = content.replace(/import\s+.*?\s+from\s+['"](?!\.).*?['"];?\s*/g, '');
|
|
339
|
+
// export キーワードを除去
|
|
340
|
+
content = content.replace(/export\s+(const|type|interface|class|function)\s+/g, '$1 ');
|
|
341
|
+
// 型宣言を除去(ランタイムでは不要)
|
|
342
|
+
content = content.replace(/^type\s+\w+\s*=\s*[^;]+;/gm, '');
|
|
343
|
+
content = content.replace(/^interface\s+\w+\s*\{[\s\S]*?\}/gm, '');
|
|
344
|
+
// コメントを除去
|
|
345
|
+
content = content.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
346
|
+
content = content.replace(/\/\/.*/g, '');
|
|
347
|
+
|
|
348
|
+
// 直接インポートされていない const 宣言を除去(displayName 等の重複を防ぐ)
|
|
349
|
+
if (importedNames) {
|
|
350
|
+
content = content.replace(/^const\s+(\w+)\s*=\s*[^;]+;/gm, (constMatch: string, varName: string) => {
|
|
351
|
+
return importedNames.has(varName) ? constMatch : '';
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return [...transitiveParts, content.trim()].filter(Boolean).join('\n\n');
|
|
356
|
+
}
|
|
357
|
+
|
|
294
358
|
/**
|
|
295
359
|
* Zodスキーマから動的にフィールド情報を抽出
|
|
296
360
|
*/
|
|
@@ -300,52 +364,33 @@ async function extractFieldsFromSchema(modelPath: string, schemaName: string): P
|
|
|
300
364
|
try {
|
|
301
365
|
// child_processでtsxを実行
|
|
302
366
|
const { execSync } = require('child_process');
|
|
303
|
-
const { tmpdir } = require('os');
|
|
304
|
-
const { join } = require('path');
|
|
305
367
|
const { writeFileSync, unlinkSync, readFileSync } = require('fs');
|
|
306
368
|
|
|
307
369
|
// モデルファイルの内容を読み込む
|
|
308
370
|
let modelContent = readFileSync(modelPath, 'utf8');
|
|
309
371
|
const modelDir = path.dirname(path.resolve(modelPath));
|
|
310
372
|
|
|
311
|
-
//
|
|
373
|
+
// ローカル相対インポートを再帰的にインライン化して保持
|
|
312
374
|
// パターン: import { categorySchema } from './category'
|
|
375
|
+
const seenPaths = new Set<string>([path.resolve(modelPath)]);
|
|
313
376
|
const localImports: string[] = [];
|
|
314
377
|
modelContent = modelContent.replace(
|
|
315
378
|
/import\s*\{([^}]+)\}\s*from\s*['"](\.[^'"]+)['"]\s*;?/g,
|
|
316
379
|
(_match: string, imports: string, importPath: string) => {
|
|
317
380
|
// インポートされた変数名を取得
|
|
318
|
-
const importedNames =
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}).filter(s => s.length > 0);
|
|
323
|
-
|
|
381
|
+
const importedNames = new Set(
|
|
382
|
+
imports.split(',').map(s => s.trim().split(/\s+as\s+/).pop()!.trim()).filter(s => s.length > 0)
|
|
383
|
+
);
|
|
384
|
+
|
|
324
385
|
// 相対パスを絶対パスに解決
|
|
325
386
|
let resolvedPath = path.resolve(modelDir, importPath);
|
|
326
387
|
if (!resolvedPath.endsWith('.ts') && !resolvedPath.endsWith('.js')) {
|
|
327
388
|
resolvedPath += '.ts';
|
|
328
389
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
refContent = refContent.replace(/import\s+.*?\s+from\s+['"](?!\.).*?['"];?\s*/g, '');
|
|
334
|
-
refContent = refContent.replace(/import\s+.*?\s+from\s+['"]\..*?['"];?\s*/g, '');
|
|
335
|
-
refContent = refContent.replace(/export\s+(const|type|interface|class|function)\s+/g, '$1 ');
|
|
336
|
-
refContent = refContent.replace(/^type\s+\w+\s*=\s*[^;]+;/gm, '');
|
|
337
|
-
refContent = refContent.replace(/^interface\s+\w+\s*\{[\s\S]*?\}/gm, '');
|
|
338
|
-
refContent = refContent.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
339
|
-
refContent = refContent.replace(/\/\/.*/g, '');
|
|
340
|
-
|
|
341
|
-
// インポートされていない const 宣言を除去(displayName 等の重複を防ぐ)
|
|
342
|
-
const importedNameSet = new Set(importedNames);
|
|
343
|
-
refContent = refContent.replace(/^const\s+(\w+)\s*=\s*[^;]+;/gm, (constMatch: string, varName: string) => {
|
|
344
|
-
return importedNameSet.has(varName) ? constMatch : '';
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
localImports.push(refContent.trim());
|
|
348
|
-
}
|
|
390
|
+
|
|
391
|
+
// 推移的なローカル依存を含めて再帰的にインライン化
|
|
392
|
+
const inlined = inlineLocalDeps(resolvedPath, importedNames, seenPaths);
|
|
393
|
+
if (inlined) localImports.push(inlined);
|
|
349
394
|
return ''; // 元のインポート文は除去
|
|
350
395
|
}
|
|
351
396
|
);
|
|
@@ -586,6 +631,102 @@ export function toCamelCase(str: string): string {
|
|
|
586
631
|
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
587
632
|
}
|
|
588
633
|
|
|
634
|
+
/**
|
|
635
|
+
* モデルファイルから connectorConfig エクスポートを静的解析で抽出する
|
|
636
|
+
*/
|
|
637
|
+
export function parseConnectorConfig(content: string): ModelConnectorConfig | undefined {
|
|
638
|
+
// export const connectorConfig = { ... } を検出
|
|
639
|
+
const connectorMatch = content.match(/export\s+const\s+connectorConfig\s*=\s*\{/);
|
|
640
|
+
if (!connectorMatch) {
|
|
641
|
+
return undefined;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// connectorConfig オブジェクトの内容を抽出
|
|
645
|
+
const startIdx = content.indexOf('{', connectorMatch.index!);
|
|
646
|
+
let braceCount = 1;
|
|
647
|
+
let endIdx = startIdx + 1;
|
|
648
|
+
while (braceCount > 0 && endIdx < content.length) {
|
|
649
|
+
if (content[endIdx] === '{') braceCount++;
|
|
650
|
+
if (content[endIdx] === '}') braceCount--;
|
|
651
|
+
endIdx++;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const objectStr = content.substring(startIdx, endIdx);
|
|
655
|
+
|
|
656
|
+
// connector 名を抽出
|
|
657
|
+
const connectorNameMatch = objectStr.match(/connector\s*:\s*['"]([^'"]+)['"]/);
|
|
658
|
+
if (!connectorNameMatch) {
|
|
659
|
+
return undefined;
|
|
660
|
+
}
|
|
661
|
+
const connector = connectorNameMatch[1];
|
|
662
|
+
|
|
663
|
+
// operations を抽出
|
|
664
|
+
const opsMatch = objectStr.match(/operations\s*:\s*\[([^\]]*)\]/);
|
|
665
|
+
const operations: string[] = [];
|
|
666
|
+
if (opsMatch) {
|
|
667
|
+
const opsStr = opsMatch[1];
|
|
668
|
+
const opEntries = opsStr.match(/['"]([^'"]+)['"]/g);
|
|
669
|
+
if (opEntries) {
|
|
670
|
+
for (const entry of opEntries) {
|
|
671
|
+
operations.push(entry.replace(/['"]/g, ''));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// table を抽出(RDB 固有)
|
|
677
|
+
const tableMatch = objectStr.match(/table\s*:\s*['"]([^'"]+)['"]/);
|
|
678
|
+
|
|
679
|
+
// idColumn を抽出(RDB 固有)
|
|
680
|
+
const idColumnMatch = objectStr.match(/idColumn\s*:\s*['"]([^'"]+)['"]/);
|
|
681
|
+
|
|
682
|
+
// endpoints を抽出(API 固有)— endpoint値に {id} 等のプレースホルダが含まれるためbrace-countingで抽出
|
|
683
|
+
let endpoints: Record<string, string> | undefined;
|
|
684
|
+
const endpointsStart = objectStr.match(/endpoints\s*:\s*\{/);
|
|
685
|
+
if (endpointsStart) {
|
|
686
|
+
const epStartIdx = objectStr.indexOf('{', endpointsStart.index!);
|
|
687
|
+
let epBraceCount = 1;
|
|
688
|
+
let epEndIdx = epStartIdx + 1;
|
|
689
|
+
while (epBraceCount > 0 && epEndIdx < objectStr.length) {
|
|
690
|
+
const ch = objectStr[epEndIdx];
|
|
691
|
+
// 文字列内の波括弧をスキップ
|
|
692
|
+
if (ch === "'" || ch === '"') {
|
|
693
|
+
epEndIdx++;
|
|
694
|
+
while (epEndIdx < objectStr.length && objectStr[epEndIdx] !== ch) {
|
|
695
|
+
epEndIdx++;
|
|
696
|
+
}
|
|
697
|
+
} else if (ch === '{') {
|
|
698
|
+
epBraceCount++;
|
|
699
|
+
} else if (ch === '}') {
|
|
700
|
+
epBraceCount--;
|
|
701
|
+
}
|
|
702
|
+
epEndIdx++;
|
|
703
|
+
}
|
|
704
|
+
const epStr = objectStr.substring(epStartIdx + 1, epEndIdx - 1);
|
|
705
|
+
endpoints = {};
|
|
706
|
+
const endpointEntries = epStr.matchAll(/(\w+)\s*:\s*['"]([^'"]+)['"]/g);
|
|
707
|
+
for (const entry of endpointEntries) {
|
|
708
|
+
endpoints[entry[1]] = entry[2];
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (tableMatch) {
|
|
713
|
+
// RDB コネクタ
|
|
714
|
+
return {
|
|
715
|
+
connector,
|
|
716
|
+
operations: operations as ModelConnectorConfig['operations'],
|
|
717
|
+
table: tableMatch[1],
|
|
718
|
+
...(idColumnMatch ? { idColumn: idColumnMatch[1] } : {}),
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// API コネクタ
|
|
723
|
+
return {
|
|
724
|
+
connector,
|
|
725
|
+
operations: operations as ModelConnectorConfig['operations'],
|
|
726
|
+
...(endpoints ? { endpoints } : {}),
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
589
730
|
/**
|
|
590
731
|
* 文字列を kebab-case に変換
|
|
591
732
|
*/
|
|
@@ -215,3 +215,157 @@ export async function DELETE(
|
|
|
215
215
|
detailRoute,
|
|
216
216
|
};
|
|
217
217
|
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* コネクタ用 BFF ルートファイルを生成(操作制限に対応)
|
|
221
|
+
* 指定された operations のみハンドラーを生成する
|
|
222
|
+
*/
|
|
223
|
+
export function generateConnectorBFFRoutes(
|
|
224
|
+
model: ModelInfo,
|
|
225
|
+
sharedPackageName: string,
|
|
226
|
+
operations: readonly string[]
|
|
227
|
+
): {
|
|
228
|
+
listRoute: string;
|
|
229
|
+
detailRoute: string;
|
|
230
|
+
} {
|
|
231
|
+
const modelName = model.name;
|
|
232
|
+
const modelCamel = toCamelCase(modelName);
|
|
233
|
+
const schemaName = model.schemaName;
|
|
234
|
+
const ops = new Set(operations);
|
|
235
|
+
|
|
236
|
+
const hasGetAll = ops.has("getAll");
|
|
237
|
+
const hasCreate = ops.has("create");
|
|
238
|
+
const hasGetById = ops.has("getById");
|
|
239
|
+
const hasUpdate = ops.has("update");
|
|
240
|
+
const hasDelete = ops.has("delete");
|
|
241
|
+
|
|
242
|
+
// --- List route ---
|
|
243
|
+
let listImports = `import { callFunction } from '@/lib/api/call-function';
|
|
244
|
+
import { ${schemaName} } from '${sharedPackageName}';
|
|
245
|
+
import { z } from 'zod/v4';`;
|
|
246
|
+
|
|
247
|
+
if (hasCreate) {
|
|
248
|
+
listImports = `import { NextRequest } from 'next/server';
|
|
249
|
+
${listImports}
|
|
250
|
+
|
|
251
|
+
const InputSchema = ${schemaName}.omit({ id: true, createdAt: true, updatedAt: true });`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let listHandlers = "";
|
|
255
|
+
if (hasGetAll) {
|
|
256
|
+
listHandlers += `
|
|
257
|
+
// GET /api/${modelCamel} - 一覧取得
|
|
258
|
+
export async function GET() {
|
|
259
|
+
return callFunction({
|
|
260
|
+
method: 'GET',
|
|
261
|
+
path: '/api/${modelCamel}',
|
|
262
|
+
responseSchema: z.array(${schemaName}),
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (hasCreate) {
|
|
269
|
+
listHandlers += `
|
|
270
|
+
// POST /api/${modelCamel} - 新規作成
|
|
271
|
+
export async function POST(request: NextRequest) {
|
|
272
|
+
const body = await request.json();
|
|
273
|
+
return callFunction({
|
|
274
|
+
method: 'POST',
|
|
275
|
+
path: '/api/${modelCamel}',
|
|
276
|
+
body,
|
|
277
|
+
inputSchema: InputSchema,
|
|
278
|
+
responseSchema: ${schemaName},
|
|
279
|
+
successStatus: 201,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const listRoute = `${listImports}
|
|
286
|
+
${listHandlers}`;
|
|
287
|
+
|
|
288
|
+
// --- Detail route ---
|
|
289
|
+
const needDetailImport = hasGetById || hasUpdate || hasDelete;
|
|
290
|
+
let detailImports = "";
|
|
291
|
+
let detailHandlers = "";
|
|
292
|
+
|
|
293
|
+
if (needDetailImport) {
|
|
294
|
+
const needNextRequest = hasUpdate;
|
|
295
|
+
detailImports = needNextRequest
|
|
296
|
+
? `import { NextRequest } from 'next/server';
|
|
297
|
+
import { callFunction } from '@/lib/api/call-function';
|
|
298
|
+
import { ${schemaName} } from '${sharedPackageName}';`
|
|
299
|
+
: `import { NextRequest } from 'next/server';
|
|
300
|
+
import { callFunction } from '@/lib/api/call-function';
|
|
301
|
+
import { ${schemaName} } from '${sharedPackageName}';`;
|
|
302
|
+
|
|
303
|
+
if (hasUpdate) {
|
|
304
|
+
detailImports += `\n\nconst InputSchema = ${schemaName}.omit({ id: true, createdAt: true, updatedAt: true });`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (hasGetById) {
|
|
308
|
+
detailHandlers += `
|
|
309
|
+
// GET /api/${modelCamel}/{id} - 詳細取得
|
|
310
|
+
export async function GET(
|
|
311
|
+
_request: NextRequest,
|
|
312
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
313
|
+
) {
|
|
314
|
+
const { id } = await params;
|
|
315
|
+
return callFunction({
|
|
316
|
+
method: 'GET',
|
|
317
|
+
path: \`/api/${modelCamel}/\${id}\`,
|
|
318
|
+
responseSchema: ${schemaName},
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (hasUpdate) {
|
|
325
|
+
detailHandlers += `
|
|
326
|
+
// PUT /api/${modelCamel}/{id} - 更新
|
|
327
|
+
export async function PUT(
|
|
328
|
+
request: NextRequest,
|
|
329
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
330
|
+
) {
|
|
331
|
+
const { id } = await params;
|
|
332
|
+
const body = await request.json();
|
|
333
|
+
return callFunction({
|
|
334
|
+
method: 'PUT',
|
|
335
|
+
path: \`/api/${modelCamel}/\${id}\`,
|
|
336
|
+
body,
|
|
337
|
+
inputSchema: InputSchema,
|
|
338
|
+
responseSchema: ${schemaName},
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (hasDelete) {
|
|
345
|
+
detailHandlers += `
|
|
346
|
+
// DELETE /api/${modelCamel}/{id} - 削除
|
|
347
|
+
export async function DELETE(
|
|
348
|
+
_request: NextRequest,
|
|
349
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
350
|
+
) {
|
|
351
|
+
const { id } = await params;
|
|
352
|
+
return callFunction({
|
|
353
|
+
method: 'DELETE',
|
|
354
|
+
path: \`/api/${modelCamel}/\${id}\`,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
`;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const detailRoute = needDetailImport
|
|
362
|
+
? `${detailImports}
|
|
363
|
+
${detailHandlers}`
|
|
364
|
+
: `// No detail operations defined for this connector model
|
|
365
|
+
`;
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
listRoute,
|
|
369
|
+
detailRoute,
|
|
370
|
+
};
|
|
371
|
+
}
|
package/src/types/index.ts
CHANGED
|
@@ -26,6 +26,52 @@ export interface UseServerFnResult<TResult> {
|
|
|
26
26
|
refetch: () => void;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// コネクタの操作種別
|
|
30
|
+
export type ConnectorOperation = "getAll" | "getById" | "create" | "update" | "delete";
|
|
31
|
+
|
|
32
|
+
// RDB コネクタ設定
|
|
33
|
+
export interface RdbConnectorConfig {
|
|
34
|
+
type: "rdb";
|
|
35
|
+
provider: "mysql" | "postgres" | "sqlserver";
|
|
36
|
+
connectionEnvVar: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// API コネクタの認証設定
|
|
40
|
+
export interface ApiConnectorAuth {
|
|
41
|
+
type: "apiKey" | "bearer" | "oauth2";
|
|
42
|
+
envVar: string;
|
|
43
|
+
placement?: "query" | "header";
|
|
44
|
+
paramName?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// API コネクタ設定
|
|
48
|
+
export interface ApiConnectorConfig {
|
|
49
|
+
type: "api";
|
|
50
|
+
baseUrlEnvVar: string;
|
|
51
|
+
auth?: ApiConnectorAuth;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// コネクタ設定の共用型
|
|
55
|
+
export type ConnectorDefinition = RdbConnectorConfig | ApiConnectorConfig;
|
|
56
|
+
|
|
57
|
+
// モデルに付与するコネクタメタデータ(RDB用)
|
|
58
|
+
export interface RdbModelConnectorConfig {
|
|
59
|
+
connector: string;
|
|
60
|
+
operations: readonly ConnectorOperation[];
|
|
61
|
+
table: string;
|
|
62
|
+
idColumn?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// モデルに付与するコネクタメタデータ(API用)
|
|
66
|
+
export interface ApiModelConnectorConfig {
|
|
67
|
+
connector: string;
|
|
68
|
+
operations: readonly ConnectorOperation[];
|
|
69
|
+
endpoints?: Partial<Record<ConnectorOperation, string>>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// モデルに付与するコネクタメタデータの共用型
|
|
73
|
+
export type ModelConnectorConfig = RdbModelConnectorConfig | ApiModelConnectorConfig;
|
|
74
|
+
|
|
29
75
|
// CLI設定の型
|
|
30
76
|
export interface SwallowKitConfig {
|
|
31
77
|
database?: {
|
|
@@ -42,4 +88,5 @@ export interface SwallowKitConfig {
|
|
|
42
88
|
credentials?: boolean;
|
|
43
89
|
};
|
|
44
90
|
};
|
|
91
|
+
connectors?: Record<string, ConnectorDefinition>;
|
|
45
92
|
}
|