swallowkit 1.0.0-beta.2 → 1.0.0-beta.20
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 +4 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +162 -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 +148 -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 +191 -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,184 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getDatabaseClient } from "./client.js";
|
|
3
|
+
import { ensureServerSide } from "./runtime-check.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @deprecated BaseModel is deprecated and will be removed in a future version.
|
|
7
|
+
*
|
|
8
|
+
* Instead of using BaseModel, define your models as pure Zod schemas:
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // shared/models/user.ts
|
|
13
|
+
* import { z } from 'zod';
|
|
14
|
+
*
|
|
15
|
+
* export const userSchema = z.object({
|
|
16
|
+
* id: z.string(),
|
|
17
|
+
* email: z.string().email(),
|
|
18
|
+
* name: z.string(),
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* export type UserType = z.infer<typeof userSchema>;
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Then use the scaffold command to generate CRUD operations:
|
|
25
|
+
* ```bash
|
|
26
|
+
* npx swallowkit scaffold shared/models/user.ts
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* This will generate:
|
|
30
|
+
* - Azure Functions with Cosmos DB bindings
|
|
31
|
+
* - Next.js BFF API routes
|
|
32
|
+
* - Type-safe React components
|
|
33
|
+
*
|
|
34
|
+
* Use the generated API from frontend:
|
|
35
|
+
* ```typescript
|
|
36
|
+
* import { api } from '@/lib/api/backend';
|
|
37
|
+
* import type { UserType } from '@myapp/shared';
|
|
38
|
+
*
|
|
39
|
+
* const users = await api.get<UserType[]>('/api/users');
|
|
40
|
+
* await api.post<UserType>('/api/users', { email: 'test@example.com', name: 'Test' });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export abstract class BaseModel {
|
|
44
|
+
/**
|
|
45
|
+
* Zod スキーマ(サブクラスで定義)
|
|
46
|
+
*/
|
|
47
|
+
static schema: z.ZodObject<any>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Cosmos DB コンテナ名(サブクラスで定義)
|
|
51
|
+
*/
|
|
52
|
+
static container: string;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* パーティションキー(サブクラスで定義)
|
|
56
|
+
* @example "/email" or "/id"
|
|
57
|
+
*/
|
|
58
|
+
static partitionKey: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* データベースクライアントを取得
|
|
62
|
+
*/
|
|
63
|
+
protected static getClient() {
|
|
64
|
+
return getDatabaseClient();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* ドキュメントを作成
|
|
69
|
+
* @param data ドキュメントデータ
|
|
70
|
+
* @returns 作成されたドキュメント
|
|
71
|
+
*/
|
|
72
|
+
static async create<T = any>(data: T): Promise<T> {
|
|
73
|
+
ensureServerSide(`${this.name}.create`);
|
|
74
|
+
|
|
75
|
+
// Zodスキーマでバリデーション
|
|
76
|
+
const validated = this.schema.parse(data);
|
|
77
|
+
|
|
78
|
+
const client = this.getClient();
|
|
79
|
+
const result = await client.createDocument(this.container, validated);
|
|
80
|
+
|
|
81
|
+
return result as T;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* ID でドキュメントを取得
|
|
86
|
+
* @param id ドキュメントID
|
|
87
|
+
* @returns ドキュメント(見つからない場合は null)
|
|
88
|
+
*/
|
|
89
|
+
static async findById<T = any>(id: string): Promise<T | null> {
|
|
90
|
+
ensureServerSide(`${this.name}.findById`);
|
|
91
|
+
|
|
92
|
+
const client = this.getClient();
|
|
93
|
+
const result = await client.getDocument(this.container, id);
|
|
94
|
+
|
|
95
|
+
if (!result) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Zodスキーマでバリデーション
|
|
100
|
+
return this.schema.parse(result) as T;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* クエリでドキュメントを検索
|
|
105
|
+
* @param query SQL クエリの WHERE 句部分(省略時は全件取得)
|
|
106
|
+
* @param parameters クエリパラメータ
|
|
107
|
+
* @returns ドキュメントの配列
|
|
108
|
+
*/
|
|
109
|
+
static async find<T = any>(query?: string, parameters?: any[]): Promise<T[]> {
|
|
110
|
+
ensureServerSide(`${this.name}.find`);
|
|
111
|
+
|
|
112
|
+
const client = this.getClient();
|
|
113
|
+
|
|
114
|
+
const fullQuery = query
|
|
115
|
+
? `SELECT * FROM c WHERE ${query}`
|
|
116
|
+
: `SELECT * FROM c`;
|
|
117
|
+
|
|
118
|
+
const results = await client.query(this.container, fullQuery, parameters);
|
|
119
|
+
|
|
120
|
+
// 各ドキュメントをZodスキーマでバリデーション
|
|
121
|
+
return results.map((result) => this.schema.parse(result)) as T[];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* ドキュメントを更新
|
|
126
|
+
* @param data 更新するドキュメントデータ(id フィールド必須)
|
|
127
|
+
* @returns 更新されたドキュメント
|
|
128
|
+
*/
|
|
129
|
+
static async update<T = any>(data: T): Promise<T> {
|
|
130
|
+
ensureServerSide(`${this.name}.update`);
|
|
131
|
+
|
|
132
|
+
// Zodスキーマでバリデーション
|
|
133
|
+
const validated = this.schema.parse(data);
|
|
134
|
+
|
|
135
|
+
if (!(validated as any).id) {
|
|
136
|
+
throw new Error(`${this.name}.update requires an 'id' field`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const client = this.getClient();
|
|
140
|
+
const result = await client.updateDocument(this.container, validated);
|
|
141
|
+
|
|
142
|
+
return result as T;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* ドキュメントを削除
|
|
147
|
+
* @param id ドキュメントID
|
|
148
|
+
*/
|
|
149
|
+
static async delete(id: string): Promise<void> {
|
|
150
|
+
ensureServerSide(`${this.name}.delete`);
|
|
151
|
+
|
|
152
|
+
const client = this.getClient();
|
|
153
|
+
await client.deleteDocument(this.container, id);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* カスタムクエリを実行
|
|
158
|
+
* @param query 完全な SQL クエリ
|
|
159
|
+
* @param parameters クエリパラメータ
|
|
160
|
+
* @returns ドキュメントの配列
|
|
161
|
+
*/
|
|
162
|
+
static async query<T = any>(query: string, parameters?: any[]): Promise<T[]> {
|
|
163
|
+
ensureServerSide(`${this.name}.query`);
|
|
164
|
+
|
|
165
|
+
const client = this.getClient();
|
|
166
|
+
const results = await client.query(this.container, query, parameters);
|
|
167
|
+
|
|
168
|
+
// 各ドキュメントをZodスキーマでバリデーション
|
|
169
|
+
return results.map((result) => this.schema.parse(result)) as T[];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get the API resource name from container name
|
|
174
|
+
* Converts container name (e.g., "Todos", "Products") to resource path (e.g., "todos", "products")
|
|
175
|
+
* @returns Resource name in lowercase plural form
|
|
176
|
+
*/
|
|
177
|
+
static getResourceName(): string {
|
|
178
|
+
if (!this.container) {
|
|
179
|
+
throw new Error(`${this.name} must define a static 'container' property`);
|
|
180
|
+
}
|
|
181
|
+
// Convert container name to lowercase (Todos -> todos, Products -> products)
|
|
182
|
+
return this.container.toLowerCase();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { CosmosClient, Database, Container } from "@azure/cosmos";
|
|
2
|
+
import { SwallowKitConfig } from "../types/index.js";
|
|
3
|
+
import { ensureServerSide } from "./runtime-check.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cosmos DB クライアントの管理
|
|
7
|
+
*/
|
|
8
|
+
export class DatabaseClient {
|
|
9
|
+
private client: CosmosClient | null = null;
|
|
10
|
+
private database: Database | null = null;
|
|
11
|
+
private containers: Map<string, Container> = new Map();
|
|
12
|
+
|
|
13
|
+
constructor(private config: SwallowKitConfig) {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* データベースに接続
|
|
17
|
+
*/
|
|
18
|
+
async connect(): Promise<void> {
|
|
19
|
+
ensureServerSide('Database connection');
|
|
20
|
+
|
|
21
|
+
if (!this.config.database?.connectionString) {
|
|
22
|
+
throw new Error("Cosmos DB connection string が設定されていません");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.client = new CosmosClient(this.config.database.connectionString);
|
|
26
|
+
|
|
27
|
+
const databaseName = this.config.database.databaseName || "SwallowKitDB";
|
|
28
|
+
const { database } = await this.client.databases.createIfNotExists({
|
|
29
|
+
id: databaseName,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
this.database = database;
|
|
33
|
+
console.log(`📦 Cosmos DB に接続しました: ${databaseName}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* コンテナを取得
|
|
38
|
+
*/
|
|
39
|
+
async getContainer(containerName: string): Promise<Container> {
|
|
40
|
+
ensureServerSide('Get container');
|
|
41
|
+
|
|
42
|
+
if (!this.database) {
|
|
43
|
+
await this.connect();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!this.containers.has(containerName)) {
|
|
47
|
+
const { container } = await this.database!.containers.createIfNotExists({
|
|
48
|
+
id: containerName,
|
|
49
|
+
partitionKey: "/id",
|
|
50
|
+
});
|
|
51
|
+
this.containers.set(containerName, container);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return this.containers.get(containerName)!;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* ドキュメントを作成
|
|
59
|
+
*/
|
|
60
|
+
async createDocument<T extends Record<string, any>>(containerName: string, document: T): Promise<T> {
|
|
61
|
+
ensureServerSide('Create document');
|
|
62
|
+
|
|
63
|
+
const container = await this.getContainer(containerName);
|
|
64
|
+
const { resource } = await container.items.create(document as any);
|
|
65
|
+
return resource as T;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* ドキュメントを取得
|
|
70
|
+
*/
|
|
71
|
+
async getDocument<T extends Record<string, any>>(containerName: string, id: string): Promise<T | null> {
|
|
72
|
+
ensureServerSide('Get document');
|
|
73
|
+
|
|
74
|
+
const container = await this.getContainer(containerName);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const { resource } = await container.item(id, id).read();
|
|
78
|
+
return resource as T || null;
|
|
79
|
+
} catch (error: any) {
|
|
80
|
+
if (error.code === 404) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* ドキュメントを更新
|
|
89
|
+
*/
|
|
90
|
+
async updateDocument<T extends Record<string, any>>(containerName: string, document: T): Promise<T> {
|
|
91
|
+
ensureServerSide('Update document');
|
|
92
|
+
|
|
93
|
+
const container = await this.getContainer(containerName);
|
|
94
|
+
const { resource } = await container.items.upsert(document as any);
|
|
95
|
+
return resource as unknown as T;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* ドキュメントを削除
|
|
100
|
+
*/
|
|
101
|
+
async deleteDocument(containerName: string, id: string): Promise<void> {
|
|
102
|
+
ensureServerSide('Delete document');
|
|
103
|
+
|
|
104
|
+
const container = await this.getContainer(containerName);
|
|
105
|
+
await container.item(id, id).delete();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* クエリを実行
|
|
110
|
+
*/
|
|
111
|
+
async query<T>(containerName: string, query: string, parameters?: any[]): Promise<T[]> {
|
|
112
|
+
ensureServerSide('Query');
|
|
113
|
+
|
|
114
|
+
const container = await this.getContainer(containerName);
|
|
115
|
+
const { resources } = await container.items.query<T>({
|
|
116
|
+
query,
|
|
117
|
+
parameters,
|
|
118
|
+
}).fetchAll();
|
|
119
|
+
|
|
120
|
+
return resources;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* グローバルデータベースクライアント
|
|
126
|
+
*/
|
|
127
|
+
let globalDatabaseClient: DatabaseClient | null = null;
|
|
128
|
+
|
|
129
|
+
export function getDatabaseClient(config?: SwallowKitConfig): DatabaseClient {
|
|
130
|
+
if (!globalDatabaseClient) {
|
|
131
|
+
if (!config) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
"DatabaseClient is not initialized. " +
|
|
134
|
+
"Please provide a config or initialize it first."
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
globalDatabaseClient = new DatabaseClient(config);
|
|
138
|
+
}
|
|
139
|
+
return globalDatabaseClient;
|
|
140
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getDatabaseClient } from "./client";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Zodスキーマベースのデータベース操作ヘルパー
|
|
6
|
+
*/
|
|
7
|
+
export class SchemaRepository<T extends Record<string, any>> {
|
|
8
|
+
constructor(
|
|
9
|
+
private containerName: string,
|
|
10
|
+
private schema: z.ZodSchema<T>
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ドキュメントを作成(バリデーション付き)
|
|
15
|
+
*/
|
|
16
|
+
async create(data: T): Promise<T> {
|
|
17
|
+
const validatedData = this.schema.parse(data);
|
|
18
|
+
const client = getDatabaseClient();
|
|
19
|
+
return client.createDocument(this.containerName, validatedData);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* ドキュメントを取得
|
|
24
|
+
*/
|
|
25
|
+
async findById(id: string): Promise<T | null> {
|
|
26
|
+
const client = getDatabaseClient();
|
|
27
|
+
const document = await client.getDocument<T>(this.containerName, id);
|
|
28
|
+
|
|
29
|
+
if (!document) return null;
|
|
30
|
+
|
|
31
|
+
// データをバリデーション
|
|
32
|
+
return this.schema.parse(document);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* ドキュメントを更新(バリデーション付き)
|
|
37
|
+
*/
|
|
38
|
+
async update(data: T): Promise<T> {
|
|
39
|
+
const validatedData = this.schema.parse(data);
|
|
40
|
+
const client = getDatabaseClient();
|
|
41
|
+
return client.updateDocument(this.containerName, validatedData);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* ドキュメントを削除
|
|
46
|
+
*/
|
|
47
|
+
async delete(id: string): Promise<void> {
|
|
48
|
+
const client = getDatabaseClient();
|
|
49
|
+
return client.deleteDocument(this.containerName, id);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 全てのドキュメントを取得
|
|
54
|
+
*/
|
|
55
|
+
async findAll(): Promise<T[]> {
|
|
56
|
+
const client = getDatabaseClient();
|
|
57
|
+
const documents = await client.query<T>(
|
|
58
|
+
this.containerName,
|
|
59
|
+
"SELECT * FROM c"
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// 全てのドキュメントをバリデーション
|
|
63
|
+
return documents.map(doc => this.schema.parse(doc));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 条件付きクエリ
|
|
68
|
+
*/
|
|
69
|
+
async findWhere(condition: string, parameters?: any[]): Promise<T[]> {
|
|
70
|
+
const client = getDatabaseClient();
|
|
71
|
+
const query = `SELECT * FROM c WHERE ${condition}`;
|
|
72
|
+
const documents = await client.query<T>(this.containerName, query, parameters);
|
|
73
|
+
|
|
74
|
+
return documents.map(doc => this.schema.parse(doc));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 簡単なリポジトリ作成ヘルパー
|
|
80
|
+
*/
|
|
81
|
+
export function createRepository<T extends Record<string, any>>(
|
|
82
|
+
containerName: string,
|
|
83
|
+
schema: z.ZodSchema<T>
|
|
84
|
+
): SchemaRepository<T> {
|
|
85
|
+
return new SchemaRepository(containerName, schema);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* よく使われるスキーマの例
|
|
90
|
+
*/
|
|
91
|
+
export const TodoSchema = z.object({
|
|
92
|
+
id: z.string(),
|
|
93
|
+
text: z.string(),
|
|
94
|
+
completed: z.boolean().default(false),
|
|
95
|
+
createdAt: z.string().default(() => new Date().toISOString()),
|
|
96
|
+
updatedAt: z.string().default(() => new Date().toISOString()),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export type Todo = z.infer<typeof TodoSchema>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Todo専用リポジトリの例
|
|
103
|
+
*/
|
|
104
|
+
export const TodoRepository = createRepository("todos", TodoSchema);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ランタイム環境の検証
|
|
3
|
+
* クライアント側でのDB操作を防ぐ
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* サーバー側で実行されているかチェック
|
|
8
|
+
*/
|
|
9
|
+
export function isServer(): boolean {
|
|
10
|
+
return typeof window === 'undefined';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* サーバー側での実行を強制
|
|
15
|
+
* クライアント側で呼ばれた場合はエラーを投げる
|
|
16
|
+
*/
|
|
17
|
+
export function ensureServerSide(operationName: string = 'Database operation'): void {
|
|
18
|
+
if (!isServer()) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`${operationName} can only be executed on the server side. ` +
|
|
21
|
+
`Please use this within Server Components, Server Actions, or API Routes. ` +
|
|
22
|
+
`Client Components cannot directly access the database.`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SwallowKit - Next.js framework optimized for Azure deployment
|
|
3
|
+
* Main exports
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Type definitions
|
|
7
|
+
export type {
|
|
8
|
+
UseServerFnOptions,
|
|
9
|
+
UseServerFnResult,
|
|
10
|
+
ServerFnMode,
|
|
11
|
+
BackendLanguage,
|
|
12
|
+
SwallowKitConfig,
|
|
13
|
+
} from "./types";
|
|
14
|
+
|
|
15
|
+
// Configuration
|
|
16
|
+
export {
|
|
17
|
+
loadConfig,
|
|
18
|
+
generateConfig,
|
|
19
|
+
getBackendLanguage,
|
|
20
|
+
getFullConfig,
|
|
21
|
+
validateConfig,
|
|
22
|
+
} from "./core/config";
|
|
23
|
+
|
|
24
|
+
// Database - Zod-based schema sharing between frontend, backend, and Cosmos DB
|
|
25
|
+
export { DatabaseClient, getDatabaseClient } from "./database/client";
|
|
26
|
+
export { SchemaRepository, createRepository, TodoSchema, TodoRepository } from "./database/repository";
|
|
27
|
+
export type { Todo } from "./database/repository";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface MachineSuccessResponse<TData> {
|
|
2
|
+
ok: true;
|
|
3
|
+
command: string;
|
|
4
|
+
data: TData;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface MachineErrorResponse {
|
|
8
|
+
ok: false;
|
|
9
|
+
command: string;
|
|
10
|
+
error: {
|
|
11
|
+
code: string;
|
|
12
|
+
message: string;
|
|
13
|
+
details?: unknown;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type MachineResponse<TData> = MachineSuccessResponse<TData> | MachineErrorResponse;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class MachineCommandError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly details?: unknown;
|
|
4
|
+
|
|
5
|
+
constructor(code: string, message: string, details?: unknown) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "MachineCommandError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.details = details;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function toMachineError(error: unknown): { code: string; message: string; details?: unknown } {
|
|
14
|
+
if (error instanceof MachineCommandError) {
|
|
15
|
+
return {
|
|
16
|
+
code: error.code,
|
|
17
|
+
message: error.message,
|
|
18
|
+
...(error.details === undefined ? {} : { details: error.details }),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (error instanceof Error) {
|
|
23
|
+
return {
|
|
24
|
+
code: "internal-error",
|
|
25
|
+
message: error.message,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
code: "internal-error",
|
|
31
|
+
message: "Unknown error",
|
|
32
|
+
details: error,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Command, CommanderError } from "commander";
|
|
2
|
+
import { createModelOperation } from "../core/operations/create-model";
|
|
3
|
+
import { runMachineScaffoldOperation } from "../core/operations/scaffold-machine";
|
|
4
|
+
import { loadProjectManifest } from "../core/project/manifest";
|
|
5
|
+
import { validateProject } from "../core/project/validation";
|
|
6
|
+
import { MachineErrorResponse, MachineResponse, MachineSuccessResponse } from "./contracts";
|
|
7
|
+
import { MachineCommandError, toMachineError } from "./errors";
|
|
8
|
+
|
|
9
|
+
function writeMachineResponse<TData>(response: MachineResponse<TData>): void {
|
|
10
|
+
process.stdout.write(`${JSON.stringify(response, null, 2)}\n`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function writeMachineSuccess<TData>(command: string, data: TData): void {
|
|
14
|
+
const response: MachineSuccessResponse<TData> = {
|
|
15
|
+
ok: true,
|
|
16
|
+
command,
|
|
17
|
+
data,
|
|
18
|
+
};
|
|
19
|
+
writeMachineResponse(response);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function writeMachineError(command: string, error: unknown): void {
|
|
23
|
+
const response: MachineErrorResponse = {
|
|
24
|
+
ok: false,
|
|
25
|
+
command,
|
|
26
|
+
error: toMachineError(error),
|
|
27
|
+
};
|
|
28
|
+
writeMachineResponse(response);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function handleMachineAction<TData>(
|
|
32
|
+
command: string,
|
|
33
|
+
action: () => Promise<TData>
|
|
34
|
+
): Promise<void> {
|
|
35
|
+
try {
|
|
36
|
+
writeMachineSuccess(command, await action());
|
|
37
|
+
} catch (error) {
|
|
38
|
+
writeMachineError(command, error);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createMachineProgram(): Command {
|
|
44
|
+
const program = new Command();
|
|
45
|
+
program
|
|
46
|
+
.name("swallowkit machine")
|
|
47
|
+
.description("SwallowKit machine-readable CLI for AI and MCP integrations")
|
|
48
|
+
.showHelpAfterError(false)
|
|
49
|
+
.configureOutput({
|
|
50
|
+
writeErr: () => undefined,
|
|
51
|
+
writeOut: () => undefined,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const inspect = new Command("inspect");
|
|
55
|
+
inspect
|
|
56
|
+
.command("project")
|
|
57
|
+
.description("Inspect SwallowKit project metadata")
|
|
58
|
+
.action(async () => {
|
|
59
|
+
await handleMachineAction("inspect-project", async () => {
|
|
60
|
+
const loaded = await loadProjectManifest();
|
|
61
|
+
return {
|
|
62
|
+
manifestSource: loaded.source,
|
|
63
|
+
diagnostics: loaded.diagnostics,
|
|
64
|
+
manifest: loaded.manifest,
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
inspect
|
|
70
|
+
.command("entities")
|
|
71
|
+
.description("Inspect SwallowKit entities")
|
|
72
|
+
.action(async () => {
|
|
73
|
+
await handleMachineAction("inspect-entities", async () => {
|
|
74
|
+
const loaded = await loadProjectManifest();
|
|
75
|
+
return {
|
|
76
|
+
manifestSource: loaded.source,
|
|
77
|
+
diagnostics: loaded.diagnostics,
|
|
78
|
+
entities: loaded.manifest.entities,
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
inspect
|
|
84
|
+
.command("routes")
|
|
85
|
+
.description("Inspect SwallowKit routes")
|
|
86
|
+
.action(async () => {
|
|
87
|
+
await handleMachineAction("inspect-routes", async () => {
|
|
88
|
+
const loaded = await loadProjectManifest();
|
|
89
|
+
return {
|
|
90
|
+
manifestSource: loaded.source,
|
|
91
|
+
diagnostics: loaded.diagnostics,
|
|
92
|
+
routes: loaded.manifest.routes,
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const validate = new Command("validate");
|
|
98
|
+
validate
|
|
99
|
+
.command("project")
|
|
100
|
+
.description("Validate SwallowKit project metadata and conventions")
|
|
101
|
+
.action(async () => {
|
|
102
|
+
await handleMachineAction("validate-project", async () => validateProject());
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const generate = new Command("generate");
|
|
106
|
+
generate
|
|
107
|
+
.command("model")
|
|
108
|
+
.description("Generate model templates with deterministic JSON output")
|
|
109
|
+
.argument("<names...>", "Model names to generate")
|
|
110
|
+
.option("--models-dir <dir>", "Models directory", "shared/models")
|
|
111
|
+
.option("--connector <name>", "Associate the models with a configured connector")
|
|
112
|
+
.option("--overwrite <mode>", "Overwrite policy: always | never", "never")
|
|
113
|
+
.action(async (names: string[], options: { modelsDir?: string; connector?: string; overwrite?: string }) => {
|
|
114
|
+
await handleMachineAction("generate-model", async () => {
|
|
115
|
+
if (options.overwrite !== "always" && options.overwrite !== "never") {
|
|
116
|
+
throw new MachineCommandError(
|
|
117
|
+
"invalid-overwrite-mode",
|
|
118
|
+
`Unsupported overwrite mode: ${options.overwrite}. Use "always" or "never".`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return createModelOperation({
|
|
123
|
+
names,
|
|
124
|
+
modelsDir: options.modelsDir,
|
|
125
|
+
connector: options.connector,
|
|
126
|
+
overwriteMode: options.overwrite,
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
generate
|
|
132
|
+
.command("scaffold")
|
|
133
|
+
.description("Generate scaffold artifacts with deterministic JSON output")
|
|
134
|
+
.argument("<model>", "Model file or model name")
|
|
135
|
+
.option("--functions-dir <dir>", "Functions directory", "functions")
|
|
136
|
+
.option("--api-dir <dir>", "API routes directory", "app/api")
|
|
137
|
+
.option("--api-only", "Generate only API artifacts", false)
|
|
138
|
+
.action(async (model: string, options: { functionsDir?: string; apiDir?: string; apiOnly?: boolean }) => {
|
|
139
|
+
await handleMachineAction("generate-scaffold", async () => runMachineScaffoldOperation({
|
|
140
|
+
model,
|
|
141
|
+
functionsDir: options.functionsDir,
|
|
142
|
+
apiDir: options.apiDir,
|
|
143
|
+
apiOnly: options.apiOnly,
|
|
144
|
+
}));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
program.addCommand(inspect);
|
|
148
|
+
program.addCommand(validate);
|
|
149
|
+
program.addCommand(generate);
|
|
150
|
+
return program;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function isMachineCommand(argv: string[]): boolean {
|
|
154
|
+
return argv[2] === "machine";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function runMachineCli(argv: string[] = process.argv): Promise<void> {
|
|
158
|
+
const program = createMachineProgram();
|
|
159
|
+
program.exitOverride();
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await program.parseAsync(argv.slice(3), { from: "user" });
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (error instanceof CommanderError) {
|
|
165
|
+
writeMachineError("machine-parse", new MachineCommandError("invalid-command", error.message));
|
|
166
|
+
process.exitCode = Number.isFinite(error.exitCode) ? error.exitCode : 1;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
writeMachineError("machine-parse", error);
|
|
171
|
+
process.exitCode = 1;
|
|
172
|
+
}
|
|
173
|
+
}
|