swallowkit 1.0.0-beta.12 → 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 +97 -6
- 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 +94 -0
- 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__/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 +176 -10
- 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 +103 -0
- 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 {
|
|
@@ -79,6 +82,9 @@ export async function parseModelFile(modelPath: string): Promise<ModelInfo> {
|
|
|
79
82
|
// displayName を抽出(例: export const displayName = 'Task')
|
|
80
83
|
const displayNameMatch = content.match(/export\s+const\s+displayName\s*=\s*['"]([^'"]+)['"]/);
|
|
81
84
|
const displayName = displayNameMatch ? displayNameMatch[1] : modelName;
|
|
85
|
+
|
|
86
|
+
// connectorConfig を抽出(外部データソース用メタデータ)
|
|
87
|
+
const connectorConfig = parseConnectorConfig(content);
|
|
82
88
|
|
|
83
89
|
// ネストしたスキーマ参照を検出
|
|
84
90
|
const nestedSchemaRefs = detectNestedSchemaRefs(modelPath, content, schemaName);
|
|
@@ -104,6 +110,7 @@ export async function parseModelFile(modelPath: string): Promise<ModelInfo> {
|
|
|
104
110
|
hasCreatedAt,
|
|
105
111
|
hasUpdatedAt,
|
|
106
112
|
nestedSchemaRefs,
|
|
113
|
+
...(connectorConfig ? { connectorConfig } : {}),
|
|
107
114
|
};
|
|
108
115
|
}
|
|
109
116
|
|
|
@@ -624,6 +631,102 @@ export function toCamelCase(str: string): string {
|
|
|
624
631
|
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
625
632
|
}
|
|
626
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
|
+
|
|
627
730
|
/**
|
|
628
731
|
* 文字列を kebab-case に変換
|
|
629
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
|
}
|