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.
Files changed (67) hide show
  1. package/README.ja.md +33 -9
  2. package/README.md +33 -9
  3. package/dist/__tests__/fixtures.d.ts +8 -0
  4. package/dist/__tests__/fixtures.d.ts.map +1 -1
  5. package/dist/__tests__/fixtures.js +60 -0
  6. package/dist/__tests__/fixtures.js.map +1 -1
  7. package/dist/cli/commands/add-connector.d.ts +20 -0
  8. package/dist/cli/commands/add-connector.d.ts.map +1 -0
  9. package/dist/cli/commands/add-connector.js +161 -0
  10. package/dist/cli/commands/add-connector.js.map +1 -0
  11. package/dist/cli/commands/create-model.d.ts +1 -0
  12. package/dist/cli/commands/create-model.d.ts.map +1 -1
  13. package/dist/cli/commands/create-model.js +65 -1
  14. package/dist/cli/commands/create-model.js.map +1 -1
  15. package/dist/cli/commands/dev.d.ts.map +1 -1
  16. package/dist/cli/commands/dev.js +49 -3
  17. package/dist/cli/commands/dev.js.map +1 -1
  18. package/dist/cli/commands/scaffold.d.ts.map +1 -1
  19. package/dist/cli/commands/scaffold.js +97 -6
  20. package/dist/cli/commands/scaffold.js.map +1 -1
  21. package/dist/cli/index.js +15 -0
  22. package/dist/cli/index.js.map +1 -1
  23. package/dist/core/config.d.ts +3 -2
  24. package/dist/core/config.d.ts.map +1 -1
  25. package/dist/core/config.js +37 -0
  26. package/dist/core/config.js.map +1 -1
  27. package/dist/core/mock/connector-mock-server.d.ts +67 -0
  28. package/dist/core/mock/connector-mock-server.d.ts.map +1 -0
  29. package/dist/core/mock/connector-mock-server.js +285 -0
  30. package/dist/core/mock/connector-mock-server.js.map +1 -0
  31. package/dist/core/mock/zod-mock-generator.d.ts +14 -0
  32. package/dist/core/mock/zod-mock-generator.d.ts.map +1 -0
  33. package/dist/core/mock/zod-mock-generator.js +163 -0
  34. package/dist/core/mock/zod-mock-generator.js.map +1 -0
  35. package/dist/core/scaffold/connector-functions-generator.d.ts +41 -0
  36. package/dist/core/scaffold/connector-functions-generator.d.ts.map +1 -0
  37. package/dist/core/scaffold/connector-functions-generator.js +1009 -0
  38. package/dist/core/scaffold/connector-functions-generator.js.map +1 -0
  39. package/dist/core/scaffold/model-parser.d.ts +6 -0
  40. package/dist/core/scaffold/model-parser.d.ts.map +1 -1
  41. package/dist/core/scaffold/model-parser.js +94 -0
  42. package/dist/core/scaffold/model-parser.js.map +1 -1
  43. package/dist/core/scaffold/nextjs-generator.d.ts +8 -0
  44. package/dist/core/scaffold/nextjs-generator.d.ts.map +1 -1
  45. package/dist/core/scaffold/nextjs-generator.js +133 -0
  46. package/dist/core/scaffold/nextjs-generator.js.map +1 -1
  47. package/dist/types/index.d.ts +31 -0
  48. package/dist/types/index.d.ts.map +1 -1
  49. package/package.json +1 -1
  50. package/src/__tests__/config.test.ts +141 -0
  51. package/src/__tests__/connector-functions-generator.test.ts +288 -0
  52. package/src/__tests__/connector-mock-server.test.ts +252 -0
  53. package/src/__tests__/connector-model-bff.test.ts +162 -0
  54. package/src/__tests__/fixtures.ts +60 -0
  55. package/src/__tests__/zod-mock-generator.test.ts +132 -0
  56. package/src/cli/commands/add-connector.ts +157 -0
  57. package/src/cli/commands/create-model.ts +72 -2
  58. package/src/cli/commands/dev.ts +55 -4
  59. package/src/cli/commands/scaffold.ts +176 -10
  60. package/src/cli/index.ts +16 -0
  61. package/src/core/config.ts +42 -1
  62. package/src/core/mock/connector-mock-server.ts +307 -0
  63. package/src/core/mock/zod-mock-generator.ts +205 -0
  64. package/src/core/scaffold/connector-functions-generator.ts +1106 -0
  65. package/src/core/scaffold/model-parser.ts +103 -0
  66. package/src/core/scaffold/nextjs-generator.ts +154 -0
  67. 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
+ }
@@ -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
  }