swallowkit 1.0.0-beta.6 → 1.0.0-beta.60
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 +86 -177
- package/README.md +86 -178
- package/dist/__tests__/fixtures.d.ts +12 -0
- package/dist/__tests__/fixtures.d.ts.map +1 -1
- package/dist/__tests__/fixtures.js +101 -0
- package/dist/__tests__/fixtures.js.map +1 -1
- package/dist/cli/commands/add-auth.d.ts +15 -0
- package/dist/cli/commands/add-auth.d.ts.map +1 -0
- package/dist/cli/commands/add-auth.js +790 -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/cosmos-worktree-isolation.d.ts +25 -0
- package/dist/cli/commands/cosmos-worktree-isolation.d.ts.map +1 -0
- package/dist/cli/commands/cosmos-worktree-isolation.js +124 -0
- package/dist/cli/commands/cosmos-worktree-isolation.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 +19 -81
- package/dist/cli/commands/create-model.js.map +1 -1
- package/dist/cli/commands/dev-seeds.d.ts +62 -0
- package/dist/cli/commands/dev-seeds.d.ts.map +1 -0
- package/dist/cli/commands/dev-seeds.js +474 -0
- package/dist/cli/commands/dev-seeds.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +38 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +733 -155
- 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 +78 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +1453 -898
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +24 -0
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +473 -90
- 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 +333 -55
- package/dist/cli/index.js.map +1 -1
- package/dist/core/config.d.ts +14 -3
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +179 -8
- 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/auth-operations.d.ts +61 -0
- package/dist/core/operations/auth-operations.d.ts.map +1 -0
- package/dist/core/operations/auth-operations.js +272 -0
- package/dist/core/operations/auth-operations.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 +185 -0
- package/dist/core/operations/create-model.js.map +1 -0
- package/dist/core/operations/file-session.d.ts +56 -0
- package/dist/core/operations/file-session.d.ts.map +1 -0
- package/dist/core/operations/file-session.js +203 -0
- package/dist/core/operations/file-session.js.map +1 -0
- package/dist/core/operations/provision-operations.d.ts +52 -0
- package/dist/core/operations/provision-operations.d.ts.map +1 -0
- package/dist/core/operations/provision-operations.js +211 -0
- package/dist/core/operations/provision-operations.js.map +1 -0
- package/dist/core/operations/runtime.d.ts +37 -0
- package/dist/core/operations/runtime.d.ts.map +1 -0
- package/dist/core/operations/runtime.js +248 -0
- package/dist/core/operations/runtime.js.map +1 -0
- package/dist/core/operations/scaffold-apply.d.ts +21 -0
- package/dist/core/operations/scaffold-apply.d.ts.map +1 -0
- package/dist/core/operations/scaffold-apply.js +57 -0
- package/dist/core/operations/scaffold-apply.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 +51 -0
- package/dist/core/operations/scaffold-machine.js.map +1 -0
- package/dist/core/operations/scaffold-plan.d.ts +43 -0
- package/dist/core/operations/scaffold-plan.d.ts.map +1 -0
- package/dist/core/operations/scaffold-plan.js +187 -0
- package/dist/core/operations/scaffold-plan.js.map +1 -0
- package/dist/core/project/agent-instructions.d.ts +29 -0
- package/dist/core/project/agent-instructions.d.ts.map +1 -0
- package/dist/core/project/agent-instructions.js +159 -0
- package/dist/core/project/agent-instructions.js.map +1 -0
- package/dist/core/project/artifacts.d.ts +58 -0
- package/dist/core/project/artifacts.d.ts.map +1 -0
- package/dist/core/project/artifacts.js +179 -0
- package/dist/core/project/artifacts.js.map +1 -0
- package/dist/core/project/boundaries.d.ts +50 -0
- package/dist/core/project/boundaries.d.ts.map +1 -0
- package/dist/core/project/boundaries.js +117 -0
- package/dist/core/project/boundaries.js.map +1 -0
- package/dist/core/project/capabilities.d.ts +58 -0
- package/dist/core/project/capabilities.d.ts.map +1 -0
- package/dist/core/project/capabilities.js +135 -0
- package/dist/core/project/capabilities.js.map +1 -0
- package/dist/core/project/drift.d.ts +29 -0
- package/dist/core/project/drift.d.ts.map +1 -0
- package/dist/core/project/drift.js +188 -0
- package/dist/core/project/drift.js.map +1 -0
- package/dist/core/project/infra.d.ts +49 -0
- package/dist/core/project/infra.d.ts.map +1 -0
- package/dist/core/project/infra.js +167 -0
- package/dist/core/project/infra.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/state.d.ts +15 -0
- package/dist/core/project/state.d.ts.map +1 -0
- package/dist/core/project/state.js +106 -0
- package/dist/core/project/state.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 +274 -0
- package/dist/core/project/validation.js.map +1 -0
- package/dist/core/project/workflows.d.ts +43 -0
- package/dist/core/project/workflows.d.ts.map +1 -0
- package/dist/core/project/workflows.js +299 -0
- package/dist/core/project/workflows.js.map +1 -0
- package/dist/core/scaffold/auth-generator.d.ts +63 -0
- package/dist/core/scaffold/auth-generator.d.ts.map +1 -0
- package/dist/core/scaffold/auth-generator.js +1774 -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 +1029 -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 +771 -69
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +21 -1
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +243 -43
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/dist/core/scaffold/native-schema-generator.d.ts +19 -0
- package/dist/core/scaffold/native-schema-generator.d.ts.map +1 -0
- package/dist/core/scaffold/native-schema-generator.js +753 -0
- package/dist/core/scaffold/native-schema-generator.js.map +1 -0
- 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 +153 -9
- 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 +17 -4
- package/dist/core/scaffold/ui-generator.d.ts.map +1 -1
- package/dist/core/scaffold/ui-generator.js +174 -34
- package/dist/core/scaffold/ui-generator.js.map +1 -1
- package/dist/core/verify/index.d.ts +69 -0
- package/dist/core/verify/index.d.ts.map +1 -0
- package/dist/core/verify/index.js +412 -0
- package/dist/core/verify/index.js.map +1 -0
- 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 +24 -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 +14 -0
- package/dist/machine/errors.d.ts.map +1 -0
- package/dist/machine/errors.js +42 -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 +566 -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 +425 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/types/index.d.ts +109 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/package-manager.d.ts +5 -2
- package/dist/utils/package-manager.d.ts.map +1 -1
- package/dist/utils/package-manager.js +30 -9
- package/dist/utils/package-manager.js.map +1 -1
- package/dist/utils/python-uv.d.ts +21 -0
- package/dist/utils/python-uv.d.ts.map +1 -0
- package/dist/utils/python-uv.js +111 -0
- package/dist/utils/python-uv.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +49 -0
- package/dist/version.js.map +1 -0
- package/package.json +9 -5
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +1139 -0
- package/src/__tests__/__snapshots__/nextjs-generator.test.ts.snap +206 -0
- package/src/__tests__/__snapshots__/ui-generator.test.ts.snap +532 -0
- package/src/__tests__/agent-instructions.test.ts +237 -0
- package/src/__tests__/agent-loop-phase34.test.ts +513 -0
- package/src/__tests__/auth.test.ts +770 -0
- package/src/__tests__/bff-swa-auth.test.ts +275 -0
- package/src/__tests__/config.test.ts +295 -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__/cosmos-worktree-isolation.test.ts +149 -0
- package/src/__tests__/dev-seeds.test.ts +236 -0
- package/src/__tests__/dev.test.ts +296 -0
- package/src/__tests__/file-session.test.ts +98 -0
- package/src/__tests__/fixtures.ts +184 -0
- package/src/__tests__/functions-generator.test.ts +237 -0
- package/src/__tests__/init.test.ts +411 -0
- package/src/__tests__/jest-globals.d.ts +8 -0
- package/src/__tests__/machine.test.ts +371 -0
- package/src/__tests__/mcp.test.ts +253 -0
- package/src/__tests__/model-parser.test.ts +81 -0
- package/src/__tests__/multi-auth.test.ts +160 -0
- package/src/__tests__/nextjs-generator.test.ts +105 -0
- package/src/__tests__/openapi-generator.test.ts +43 -0
- package/src/__tests__/package-manager.test.ts +227 -0
- package/src/__tests__/plan-apply.test.ts +499 -0
- package/src/__tests__/python-uv.test.ts +48 -0
- package/src/__tests__/scaffold.test.ts +302 -0
- package/src/__tests__/string-utils.test.ts +75 -0
- package/src/__tests__/tsconfig.json +15 -0
- package/src/__tests__/ui-generator.test.ts +248 -0
- package/src/__tests__/zod-mock-generator.test.ts +132 -0
- package/src/cli/commands/add-auth.ts +853 -0
- package/src/cli/commands/add-connector.ts +158 -0
- package/src/cli/commands/cosmos-worktree-isolation.ts +130 -0
- package/src/cli/commands/create-model.ts +62 -0
- package/src/cli/commands/dev-seeds.ts +629 -0
- package/src/cli/commands/dev.ts +1252 -0
- package/src/cli/commands/index.ts +9 -0
- package/src/cli/commands/init.ts +3541 -0
- package/src/cli/commands/provision.ts +193 -0
- package/src/cli/commands/scaffold.ts +1081 -0
- package/src/cli/index.ts +350 -0
- package/src/core/config.ts +380 -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/auth-operations.ts +360 -0
- package/src/core/operations/create-model.ts +191 -0
- package/src/core/operations/file-session.ts +222 -0
- package/src/core/operations/provision-operations.ts +297 -0
- package/src/core/operations/runtime.ts +258 -0
- package/src/core/operations/scaffold-apply.ts +106 -0
- package/src/core/operations/scaffold-machine.ts +77 -0
- package/src/core/operations/scaffold-plan.ts +215 -0
- package/src/core/project/agent-instructions.ts +167 -0
- package/src/core/project/agent-templates/AGENTS.md +502 -0
- package/src/core/project/agent-templates/CLAUDE.md +31 -0
- package/src/core/project/agent-templates/azure-functions.instructions.md +22 -0
- package/src/core/project/agent-templates/bff-routes.instructions.md +18 -0
- package/src/core/project/agent-templates/copilot-instructions.md +27 -0
- package/src/core/project/agent-templates/shared-models.instructions.md +72 -0
- package/src/core/project/artifacts.ts +205 -0
- package/src/core/project/boundaries.ts +170 -0
- package/src/core/project/capabilities.ts +193 -0
- package/src/core/project/drift.ts +202 -0
- package/src/core/project/infra.ts +196 -0
- package/src/core/project/manifest.ts +402 -0
- package/src/core/project/state.ts +77 -0
- package/src/core/project/validation.ts +295 -0
- package/src/core/project/workflows.ts +295 -0
- package/src/core/scaffold/auth-generator.ts +1824 -0
- package/src/core/scaffold/connector-functions-generator.ts +1133 -0
- package/src/core/scaffold/functions-generator.ts +972 -0
- package/src/core/scaffold/model-parser.ts +854 -0
- package/src/core/scaffold/native-schema-generator.ts +904 -0
- package/src/core/scaffold/nextjs-generator.ts +382 -0
- package/src/core/scaffold/openapi-generator.ts +212 -0
- package/src/core/scaffold/ui-generator.ts +1100 -0
- package/src/core/verify/index.ts +468 -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 +33 -0
- package/src/machine/errors.ts +45 -0
- package/src/machine/index.ts +690 -0
- package/src/mcp/index.ts +423 -0
- package/src/types/index.ts +182 -0
- package/src/utils/package-manager.ts +246 -0
- package/src/utils/python-uv.ts +96 -0
- package/src/version.ts +13 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify — 生成後の検証チェックを実行し、機械可読な結果を返す。
|
|
3
|
+
*
|
|
4
|
+
* 提供チェック:
|
|
5
|
+
* - structure: プロジェクト構造規約 (validateProject)
|
|
6
|
+
* - drift: 生成物と現在状態の乖離 (detectDrift)
|
|
7
|
+
* - typecheck: TypeScript 型チェック (tsc --noEmit / typecheck script)
|
|
8
|
+
* - build/lint/test: package.json scripts に同名 script がある場合のみ実行 (なければ skip)
|
|
9
|
+
* - custom: swallowkit.config の verify.checks で定義されたプロジェクト固有チェック
|
|
10
|
+
*
|
|
11
|
+
* 既定実行セットは structure/drift/typecheck + custom。build/lint/test は --checks で明示指定。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as fs from "fs";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
import { spawnSync } from "child_process";
|
|
17
|
+
import { loadConfig } from "../config";
|
|
18
|
+
import { detectDrift, DriftFinding } from "../project/drift";
|
|
19
|
+
import { saveLastVerifyState } from "../project/state";
|
|
20
|
+
import { ProjectViolation, validateProject } from "../project/validation";
|
|
21
|
+
import { CustomVerifyCheck } from "../../types";
|
|
22
|
+
import { detectFromProject, getCommands } from "../../utils/package-manager";
|
|
23
|
+
|
|
24
|
+
export type VerifyCheckStatus = "pass" | "fail" | "skip" | "error";
|
|
25
|
+
|
|
26
|
+
export interface VerifyCheckEvidence {
|
|
27
|
+
command?: string;
|
|
28
|
+
exitCode?: number;
|
|
29
|
+
violations?: ProjectViolation[];
|
|
30
|
+
findings?: DriftFinding[];
|
|
31
|
+
logTail?: string[];
|
|
32
|
+
/** --compact で抑制された info-severity findings の件数。 */
|
|
33
|
+
suppressedInfoFindings?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface VerifyCheckResult {
|
|
37
|
+
id: string;
|
|
38
|
+
title: string;
|
|
39
|
+
status: VerifyCheckStatus;
|
|
40
|
+
durationMs: number;
|
|
41
|
+
fixable: boolean;
|
|
42
|
+
evidence: VerifyCheckEvidence;
|
|
43
|
+
suggestedActions: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface VerifySummary {
|
|
47
|
+
passed: number;
|
|
48
|
+
failed: number;
|
|
49
|
+
skipped: number;
|
|
50
|
+
errors: number;
|
|
51
|
+
/** true = すべてのチェックが pass または skip */
|
|
52
|
+
done: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface VerifyResult {
|
|
56
|
+
checks: VerifyCheckResult[];
|
|
57
|
+
summary: VerifySummary;
|
|
58
|
+
verifiedAt: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const AVAILABLE_VERIFY_CHECKS = ["structure", "drift", "typecheck", "build", "lint", "test"] as const;
|
|
62
|
+
export type VerifyCheckId = (typeof AVAILABLE_VERIFY_CHECKS)[number];
|
|
63
|
+
|
|
64
|
+
/** 既定で実行される built-in チェック。build/lint/test は明示指定時のみ。 */
|
|
65
|
+
export const DEFAULT_VERIFY_CHECKS = ["structure", "drift", "typecheck"] as const;
|
|
66
|
+
|
|
67
|
+
async function runStructureCheck(projectRoot: string): Promise<VerifyCheckResult> {
|
|
68
|
+
const startedAt = Date.now();
|
|
69
|
+
try {
|
|
70
|
+
const result = await validateProject(projectRoot);
|
|
71
|
+
const errors = result.violations.filter((violation) => violation.severity === "error");
|
|
72
|
+
return {
|
|
73
|
+
id: "structure",
|
|
74
|
+
title: "Project structure conventions",
|
|
75
|
+
status: errors.length === 0 ? "pass" : "fail",
|
|
76
|
+
durationMs: Date.now() - startedAt,
|
|
77
|
+
fixable: true,
|
|
78
|
+
evidence: { violations: result.violations },
|
|
79
|
+
suggestedActions:
|
|
80
|
+
errors.length === 0
|
|
81
|
+
? []
|
|
82
|
+
: errors.map((violation) => violation.suggestedFix ?? violation.message),
|
|
83
|
+
};
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return {
|
|
86
|
+
id: "structure",
|
|
87
|
+
title: "Project structure conventions",
|
|
88
|
+
status: "error",
|
|
89
|
+
durationMs: Date.now() - startedAt,
|
|
90
|
+
fixable: false,
|
|
91
|
+
evidence: { logTail: [error instanceof Error ? error.message : String(error)] },
|
|
92
|
+
suggestedActions: ["Run this command inside a SwallowKit project root."],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function runDriftCheck(projectRoot: string): Promise<VerifyCheckResult> {
|
|
98
|
+
const startedAt = Date.now();
|
|
99
|
+
try {
|
|
100
|
+
const result = await detectDrift(projectRoot);
|
|
101
|
+
const errors = result.findings.filter((finding) => finding.severity === "error");
|
|
102
|
+
return {
|
|
103
|
+
id: "drift",
|
|
104
|
+
title: "Generated artifact drift",
|
|
105
|
+
status: errors.length === 0 ? "pass" : "fail",
|
|
106
|
+
durationMs: Date.now() - startedAt,
|
|
107
|
+
fixable: true,
|
|
108
|
+
evidence: { findings: result.findings },
|
|
109
|
+
suggestedActions: errors.map((finding) => finding.repairAction),
|
|
110
|
+
};
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return {
|
|
113
|
+
id: "drift",
|
|
114
|
+
title: "Generated artifact drift",
|
|
115
|
+
status: "error",
|
|
116
|
+
durationMs: Date.now() - startedAt,
|
|
117
|
+
fixable: false,
|
|
118
|
+
evidence: { logTail: [error instanceof Error ? error.message : String(error)] },
|
|
119
|
+
suggestedActions: [],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function tailLines(text: string, count: number): string[] {
|
|
125
|
+
return text
|
|
126
|
+
.split(/\r?\n/)
|
|
127
|
+
.filter((line) => line.trim().length > 0)
|
|
128
|
+
.slice(-count);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface CommandCheckSpec {
|
|
132
|
+
id: string;
|
|
133
|
+
title: string;
|
|
134
|
+
command: string;
|
|
135
|
+
args: string[];
|
|
136
|
+
timeoutMs?: number;
|
|
137
|
+
failAction: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function runCommandCheck(projectRoot: string, spec: CommandCheckSpec): VerifyCheckResult {
|
|
141
|
+
const startedAt = Date.now();
|
|
142
|
+
const commandLabel = [spec.command, ...spec.args].join(" ").trim();
|
|
143
|
+
try {
|
|
144
|
+
const result = spawnSync(spec.command, spec.args, {
|
|
145
|
+
cwd: projectRoot,
|
|
146
|
+
shell: true,
|
|
147
|
+
encoding: "utf-8",
|
|
148
|
+
timeout: spec.timeoutMs ?? 5 * 60 * 1000,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
|
|
152
|
+
const exitCode = typeof result.status === "number" ? result.status : -1;
|
|
153
|
+
|
|
154
|
+
if (result.error) {
|
|
155
|
+
return {
|
|
156
|
+
id: spec.id,
|
|
157
|
+
title: spec.title,
|
|
158
|
+
status: "error",
|
|
159
|
+
durationMs: Date.now() - startedAt,
|
|
160
|
+
fixable: false,
|
|
161
|
+
evidence: { command: commandLabel, logTail: [result.error.message] },
|
|
162
|
+
suggestedActions: [`Ensure "${commandLabel}" can run in this project.`],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
id: spec.id,
|
|
168
|
+
title: spec.title,
|
|
169
|
+
status: exitCode === 0 ? "pass" : "fail",
|
|
170
|
+
durationMs: Date.now() - startedAt,
|
|
171
|
+
fixable: true,
|
|
172
|
+
evidence: {
|
|
173
|
+
command: commandLabel,
|
|
174
|
+
exitCode,
|
|
175
|
+
logTail: tailLines(output, 50),
|
|
176
|
+
},
|
|
177
|
+
suggestedActions: exitCode === 0 ? [] : [spec.failAction],
|
|
178
|
+
};
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return {
|
|
181
|
+
id: spec.id,
|
|
182
|
+
title: spec.title,
|
|
183
|
+
status: "error",
|
|
184
|
+
durationMs: Date.now() - startedAt,
|
|
185
|
+
fixable: false,
|
|
186
|
+
evidence: { command: commandLabel, logTail: [error instanceof Error ? error.message : String(error)] },
|
|
187
|
+
suggestedActions: [],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function readPackageScripts(projectRoot: string): Record<string, string> {
|
|
193
|
+
const packageJsonPath = path.join(projectRoot, "package.json");
|
|
194
|
+
if (!fs.existsSync(packageJsonPath)) return {};
|
|
195
|
+
try {
|
|
196
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) as { scripts?: Record<string, string> };
|
|
197
|
+
return pkg.scripts ?? {};
|
|
198
|
+
} catch {
|
|
199
|
+
return {};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function runTypecheckCheck(projectRoot: string): VerifyCheckResult {
|
|
204
|
+
const startedAt = Date.now();
|
|
205
|
+
|
|
206
|
+
if (!fs.existsSync(path.join(projectRoot, "tsconfig.json"))) {
|
|
207
|
+
return {
|
|
208
|
+
id: "typecheck",
|
|
209
|
+
title: "TypeScript type check",
|
|
210
|
+
status: "skip",
|
|
211
|
+
durationMs: Date.now() - startedAt,
|
|
212
|
+
fixable: false,
|
|
213
|
+
evidence: { logTail: ["tsconfig.json not found; skipping typecheck."] },
|
|
214
|
+
suggestedActions: [],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// typecheck は shared のビルド済み dist/ を参照する。古い dist による
|
|
219
|
+
// 「no exported member」誤検知を防ぐため、先に shared をビルドする。
|
|
220
|
+
const sharedBuild = runSharedBuildIfPresent(projectRoot);
|
|
221
|
+
if (sharedBuild.ran && !sharedBuild.ok) {
|
|
222
|
+
return {
|
|
223
|
+
id: "typecheck",
|
|
224
|
+
title: "TypeScript type check",
|
|
225
|
+
status: "fail",
|
|
226
|
+
durationMs: Date.now() - startedAt,
|
|
227
|
+
fixable: true,
|
|
228
|
+
evidence: { command: sharedBuild.command, logTail: sharedBuild.logTail },
|
|
229
|
+
suggestedActions: [
|
|
230
|
+
"Fix the shared package build errors first; typecheck resolves the shared package from its built dist/.",
|
|
231
|
+
],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const scripts = readPackageScripts(projectRoot);
|
|
236
|
+
let command: string;
|
|
237
|
+
let args: string[];
|
|
238
|
+
if (typeof scripts.typecheck === "string") {
|
|
239
|
+
const pm = detectFromProject(projectRoot);
|
|
240
|
+
command = getCommands(pm).name;
|
|
241
|
+
args = ["run", "typecheck"];
|
|
242
|
+
} else {
|
|
243
|
+
command = "npx";
|
|
244
|
+
args = ["tsc", "--noEmit"];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return runCommandCheck(projectRoot, {
|
|
248
|
+
id: "typecheck",
|
|
249
|
+
title: "TypeScript type check",
|
|
250
|
+
command,
|
|
251
|
+
args,
|
|
252
|
+
failAction: "Fix the TypeScript errors listed in evidence.logTail, then re-run verify.",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const SCRIPT_CHECK_TITLES: Record<string, string> = {
|
|
257
|
+
build: "Project build",
|
|
258
|
+
lint: "Lint",
|
|
259
|
+
test: "Test suite",
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
interface SharedBuildOutcome {
|
|
263
|
+
ran: boolean;
|
|
264
|
+
ok: boolean;
|
|
265
|
+
command?: string;
|
|
266
|
+
logTail?: string[];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** shared/package.json に build script があれば実行する (なければ no-op)。 */
|
|
270
|
+
function runSharedBuildIfPresent(projectRoot: string): SharedBuildOutcome {
|
|
271
|
+
const sharedPackageJsonPath = path.join(projectRoot, "shared", "package.json");
|
|
272
|
+
if (!fs.existsSync(sharedPackageJsonPath)) return { ran: false, ok: true };
|
|
273
|
+
try {
|
|
274
|
+
const pkg = JSON.parse(fs.readFileSync(sharedPackageJsonPath, "utf-8")) as { scripts?: Record<string, string> };
|
|
275
|
+
if (typeof pkg.scripts?.build !== "string") return { ran: false, ok: true };
|
|
276
|
+
} catch {
|
|
277
|
+
return { ran: false, ok: true };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const pm = detectFromProject(projectRoot);
|
|
281
|
+
const command = getCommands(pm).name;
|
|
282
|
+
const result = spawnSync(command, ["run", "build"], {
|
|
283
|
+
cwd: path.join(projectRoot, "shared"),
|
|
284
|
+
shell: true,
|
|
285
|
+
encoding: "utf-8",
|
|
286
|
+
timeout: 5 * 60 * 1000,
|
|
287
|
+
});
|
|
288
|
+
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
|
|
289
|
+
const ok = result.status === 0 && !result.error;
|
|
290
|
+
return {
|
|
291
|
+
ran: true,
|
|
292
|
+
ok,
|
|
293
|
+
command: `${command} run build (in shared/)`,
|
|
294
|
+
logTail: ok ? undefined : tailLines(result.error ? `${output}\n${result.error.message}` : output, 50),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function runScriptCheck(projectRoot: string, scriptName: "build" | "lint" | "test"): VerifyCheckResult {
|
|
299
|
+
const startedAt = Date.now();
|
|
300
|
+
const scripts = readPackageScripts(projectRoot);
|
|
301
|
+
const title = SCRIPT_CHECK_TITLES[scriptName];
|
|
302
|
+
|
|
303
|
+
if (typeof scripts[scriptName] !== "string") {
|
|
304
|
+
return {
|
|
305
|
+
id: scriptName,
|
|
306
|
+
title,
|
|
307
|
+
status: "skip",
|
|
308
|
+
durationMs: Date.now() - startedAt,
|
|
309
|
+
fixable: false,
|
|
310
|
+
evidence: { logTail: [`No "${scriptName}" script in package.json; skipping.`] },
|
|
311
|
+
suggestedActions: [],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const pm = detectFromProject(projectRoot);
|
|
316
|
+
return runCommandCheck(projectRoot, {
|
|
317
|
+
id: scriptName,
|
|
318
|
+
title,
|
|
319
|
+
command: getCommands(pm).name,
|
|
320
|
+
args: ["run", scriptName],
|
|
321
|
+
timeoutMs: 10 * 60 * 1000,
|
|
322
|
+
failAction: `Fix the ${scriptName} failures listed in evidence.logTail, then re-run verify.`,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const CUSTOM_CHECK_ID = /^[a-z][a-z0-9-]*$/;
|
|
327
|
+
|
|
328
|
+
/** swallowkit.config の verify.checks を projectRoot 基準で静かに読み込む。 */
|
|
329
|
+
export function loadCustomVerifyChecks(projectRoot: string): CustomVerifyCheck[] {
|
|
330
|
+
const configNames = ["swallowkit.config.json", "swallowkit.config.js", ".swallowkitrc.json"];
|
|
331
|
+
for (const name of configNames) {
|
|
332
|
+
const configPath = path.join(projectRoot, name);
|
|
333
|
+
if (!fs.existsSync(configPath)) continue;
|
|
334
|
+
try {
|
|
335
|
+
const checks = loadConfig(configPath, false, true).verify?.checks ?? [];
|
|
336
|
+
return checks.filter(
|
|
337
|
+
(check) =>
|
|
338
|
+
typeof check?.id === "string" &&
|
|
339
|
+
CUSTOM_CHECK_ID.test(check.id) &&
|
|
340
|
+
!(AVAILABLE_VERIFY_CHECKS as readonly string[]).includes(check.id) &&
|
|
341
|
+
typeof check.command === "string" &&
|
|
342
|
+
check.command.trim().length > 0
|
|
343
|
+
);
|
|
344
|
+
} catch {
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function runCustomCheck(projectRoot: string, check: CustomVerifyCheck): VerifyCheckResult {
|
|
352
|
+
return runCommandCheck(projectRoot, {
|
|
353
|
+
id: check.id,
|
|
354
|
+
title: check.title ?? `Custom check: ${check.id}`,
|
|
355
|
+
command: check.command,
|
|
356
|
+
args: [],
|
|
357
|
+
timeoutMs: check.timeoutMs,
|
|
358
|
+
failAction: `Fix the failures reported by "${check.command}", then re-run verify.`,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export async function runVerify(
|
|
363
|
+
checkIds?: string[],
|
|
364
|
+
projectRoot: string = process.cwd()
|
|
365
|
+
): Promise<VerifyResult> {
|
|
366
|
+
const customChecks = loadCustomVerifyChecks(projectRoot);
|
|
367
|
+
const customIds = customChecks.map((check) => check.id);
|
|
368
|
+
const availableIds = [...AVAILABLE_VERIFY_CHECKS, ...customIds];
|
|
369
|
+
|
|
370
|
+
const requested = (checkIds && checkIds.length > 0
|
|
371
|
+
? checkIds
|
|
372
|
+
: [...DEFAULT_VERIFY_CHECKS, ...customIds]
|
|
373
|
+
).map((id) => id.trim()).filter(Boolean);
|
|
374
|
+
|
|
375
|
+
const unknown = requested.filter((id) => !availableIds.includes(id));
|
|
376
|
+
if (unknown.length > 0) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
`Unknown verify check(s): ${unknown.join(", ")}. Available checks: ${availableIds.join(", ")}`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const checks: VerifyCheckResult[] = [];
|
|
383
|
+
for (const id of requested) {
|
|
384
|
+
if (id === "structure") {
|
|
385
|
+
checks.push(await runStructureCheck(projectRoot));
|
|
386
|
+
} else if (id === "drift") {
|
|
387
|
+
checks.push(await runDriftCheck(projectRoot));
|
|
388
|
+
} else if (id === "typecheck") {
|
|
389
|
+
checks.push(runTypecheckCheck(projectRoot));
|
|
390
|
+
} else if (id === "build" || id === "lint" || id === "test") {
|
|
391
|
+
checks.push(runScriptCheck(projectRoot, id));
|
|
392
|
+
} else {
|
|
393
|
+
const custom = customChecks.find((check) => check.id === id);
|
|
394
|
+
if (custom) checks.push(runCustomCheck(projectRoot, custom));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const summary: VerifySummary = {
|
|
399
|
+
passed: checks.filter((check) => check.status === "pass").length,
|
|
400
|
+
failed: checks.filter((check) => check.status === "fail").length,
|
|
401
|
+
skipped: checks.filter((check) => check.status === "skip").length,
|
|
402
|
+
errors: checks.filter((check) => check.status === "error").length,
|
|
403
|
+
done: checks.every((check) => check.status === "pass" || check.status === "skip"),
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const result: VerifyResult = {
|
|
407
|
+
checks,
|
|
408
|
+
summary,
|
|
409
|
+
verifiedAt: new Date().toISOString(),
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
saveLastVerifyState(result, projectRoot);
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Agent 消費向けに info-severity の drift findings (「user-owned が編集されたが想定内」等)
|
|
418
|
+
* を除去したコンパクトな結果を返す。判定 (summary) は変わらない。
|
|
419
|
+
*/
|
|
420
|
+
export function compactVerifyResult(result: VerifyResult): VerifyResult {
|
|
421
|
+
return {
|
|
422
|
+
...result,
|
|
423
|
+
checks: result.checks.map((check) => {
|
|
424
|
+
const findings = check.evidence.findings;
|
|
425
|
+
if (!findings || findings.length === 0) return check;
|
|
426
|
+
const kept = findings.filter((finding) => finding.severity !== "info");
|
|
427
|
+
const suppressed = findings.length - kept.length;
|
|
428
|
+
if (suppressed === 0) return check;
|
|
429
|
+
return {
|
|
430
|
+
...check,
|
|
431
|
+
evidence: { ...check.evidence, findings: kept, suppressedInfoFindings: suppressed },
|
|
432
|
+
};
|
|
433
|
+
}),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export interface FailureExplanation {
|
|
438
|
+
checkId: string;
|
|
439
|
+
title: string;
|
|
440
|
+
status: VerifyCheckStatus;
|
|
441
|
+
verifiedAt: string;
|
|
442
|
+
evidence: VerifyCheckEvidence;
|
|
443
|
+
suggestedActions: string[];
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export function explainVerifyFailure(
|
|
447
|
+
lastVerify: VerifyResult,
|
|
448
|
+
checkId?: string
|
|
449
|
+
): FailureExplanation[] {
|
|
450
|
+
const targets = checkId
|
|
451
|
+
? lastVerify.checks.filter((check) => check.id === checkId)
|
|
452
|
+
: lastVerify.checks.filter((check) => check.status === "fail" || check.status === "error");
|
|
453
|
+
|
|
454
|
+
if (checkId && targets.length === 0) {
|
|
455
|
+
throw new Error(
|
|
456
|
+
`Unknown check "${checkId}" in the last verify result. Available: ${lastVerify.checks.map((check) => check.id).join(", ")}`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return targets.map((check) => ({
|
|
461
|
+
checkId: check.id,
|
|
462
|
+
title: check.title,
|
|
463
|
+
status: check.status,
|
|
464
|
+
verifiedAt: lastVerify.verifiedAt,
|
|
465
|
+
evidence: check.evidence,
|
|
466
|
+
suggestedActions: check.suggestedActions,
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
@@ -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
|
+
}
|