supatool 0.1.23 → 0.3.0

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.
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateCrudFromModel = generateCrudFromModel;
7
+ // CRUD関数TypeScriptコード自動生成(Supabase実動作対応)
8
+ // 日本語コメント
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ /**
12
+ * dataSchemaまたはmodels/tablesから各テーブルごとのCRUD関数TypeScriptファイルを生成
13
+ * @param model モデルオブジェクト
14
+ * @param outDir 出力先ディレクトリ
15
+ */
16
+ function generateCrudFromModel(model, outDir) {
17
+ if (!fs_1.default.existsSync(outDir)) {
18
+ fs_1.default.mkdirSync(outDir, { recursive: true });
19
+ }
20
+ let tables = [];
21
+ if (Array.isArray(model.dataSchema)) {
22
+ tables = model.dataSchema.map((t) => ({ tableName: t.tableName || t.raw, ...t }));
23
+ }
24
+ else if (Array.isArray(model.models)) {
25
+ for (const m of model.models) {
26
+ if (m.tables) {
27
+ for (const [tableName, table] of Object.entries(m.tables)) {
28
+ tables.push(Object.assign({ tableName }, table));
29
+ }
30
+ }
31
+ }
32
+ }
33
+ for (const tableObj of tables) {
34
+ if (tableObj.skipCreate)
35
+ continue;
36
+ const tableName = tableObj.tableName;
37
+ let code = `// 自動生成: ${tableName}用CRUD関数\n\n`;
38
+ code += `import { supabase } from '../client';\n`;
39
+ code += `import type { ${tableName} } from '../types';\n\n`;
40
+ code += `/** 全件取得 */\n`;
41
+ code += `export async function getAll${capitalize(tableName)}(): Promise<${tableName}[]> {\n`;
42
+ code += ` const { data, error } = await supabase.from('${tableName}').select('*');\n`;
43
+ code += ` if (error) throw error;\n`;
44
+ code += ` return data as ${tableName}[];\n`;
45
+ code += `}\n\n`;
46
+ code += `/** IDで1件取得 */\n`;
47
+ code += `export async function get${capitalize(tableName)}ById(id: string): Promise<${tableName} | null> {\n`;
48
+ code += ` const { data, error } = await supabase.from('${tableName}').select('*').eq('id', id).single();\n`;
49
+ code += ` if (error) throw error;\n`;
50
+ code += ` return data as ${tableName} | null;\n`;
51
+ code += `}\n`;
52
+ fs_1.default.writeFileSync(path_1.default.join(outDir, `${tableName}.ts`), code);
53
+ }
54
+ }
55
+ function capitalize(str) {
56
+ return str.charAt(0).toUpperCase() + str.slice(1);
57
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateTableDocMarkdown = generateTableDocMarkdown;
7
+ exports.generateRelationsMarkdown = generateRelationsMarkdown;
8
+ // ドキュメント生成(最小雛形)
9
+ // 日本語コメント
10
+ const path_1 = __importDefault(require("path"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ /**
13
+ * テーブル定義書(Markdown)を生成して保存
14
+ * @param model モデルオブジェクト
15
+ * @param outPath 出力先パス
16
+ */
17
+ function generateTableDocMarkdown(model, outPath) {
18
+ // 出力先ディレクトリを作成
19
+ const dir = path_1.default.dirname(outPath);
20
+ if (!fs_1.default.existsSync(dir)) {
21
+ fs_1.default.mkdirSync(dir, { recursive: true });
22
+ }
23
+ let md = '# テーブル定義書\n\n';
24
+ for (const m of model.models) {
25
+ const tables = m.tables || {};
26
+ for (const [tableName, table] of Object.entries(tables)) {
27
+ const t = table;
28
+ let skipNote = t.skipCreate ? '(作成不要: Supabase組み込み)' : '';
29
+ md += `## ${tableName}${skipNote}\n`;
30
+ if (t.description)
31
+ md += `${t.description}\n`;
32
+ md += '\n| カラム | 型 | 主キー | NotNull | デフォルト | ラベル |\n|---|---|---|---|---|---|\n';
33
+ for (const [colName, col] of Object.entries(t.fields || {})) {
34
+ const c = col;
35
+ md += `| ${colName} | ${c.type || ''} | ${c.primary ? '★' : ''} | ${c.notNull ? '○' : ''} | ${c.default || ''} | ${c.label || ''} |\n`;
36
+ }
37
+ md += '\n';
38
+ }
39
+ }
40
+ fs_1.default.writeFileSync(outPath, md);
41
+ }
42
+ /**
43
+ * リレーション一覧(Markdown)を生成して保存
44
+ * @param model モデルオブジェクト
45
+ * @param outPath 出力先パス
46
+ */
47
+ function generateRelationsMarkdown(model, outPath) {
48
+ const dir = path_1.default.dirname(outPath);
49
+ if (!fs_1.default.existsSync(dir)) {
50
+ fs_1.default.mkdirSync(dir, { recursive: true });
51
+ }
52
+ let md = '# リレーション一覧\n\n| テーブル | 関係 | 対象 | 外部キー |\n|---|---|---|---|\n';
53
+ for (const m of model.models) {
54
+ const tables = m.tables || {};
55
+ for (const [tableName, table] of Object.entries(tables)) {
56
+ const t = table;
57
+ for (const [relName, rel] of Object.entries(t.relations || {})) {
58
+ const r = rel;
59
+ md += `| ${tableName} | ${r.type || ''} | ${r.target || ''} | ${r.foreignKey || ''} |\n`;
60
+ }
61
+ }
62
+ }
63
+ fs_1.default.writeFileSync(outPath, md);
64
+ }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateRlsSqlFromModel = generateRlsSqlFromModel;
7
+ // RLS/セキュリティポリシーSQL自動生成(最小雛形)
8
+ // Todo: スキーマを設定可能とする
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ /**
12
+ * モデルからRLS/セキュリティポリシーSQLを生成
13
+ * @param model モデルオブジェクト
14
+ * @param outPath 出力先パス
15
+ */
16
+ function generateRlsSqlFromModel(model, outPath) {
17
+ const dir = path_1.default.dirname(outPath);
18
+ if (!fs_1.default.existsSync(dir)) {
19
+ fs_1.default.mkdirSync(dir, { recursive: true });
20
+ }
21
+ // dataSchema/models両対応: テーブル一覧取得
22
+ let tables = [];
23
+ if (Array.isArray(model.dataSchema)) {
24
+ tables = model.dataSchema.map((t) => ({ tableName: t.tableName || t.raw, ...t }));
25
+ }
26
+ else if (Array.isArray(model.models)) {
27
+ for (const m of model.models) {
28
+ if (m.tables) {
29
+ for (const [tableName, table] of Object.entries(m.tables)) {
30
+ tables.push(Object.assign({ tableName }, table));
31
+ }
32
+ }
33
+ }
34
+ }
35
+ let sql = '-- 自動生成: RLS/セキュリティポリシーDDL\n\n';
36
+ const security = model.security || {};
37
+ // roles定義があればロールマスタ・ユーザーロールDDLも自動生成
38
+ if (model.roles && Array.isArray(model.roles)) {
39
+ sql += '-- ロールマスタ・ユーザーロールDDL(自動生成)\n';
40
+ sql += `CREATE TABLE IF NOT EXISTS m_roles (\n id uuid PRIMARY KEY,\n name text NOT NULL\n);\n\n`;
41
+ sql += `CREATE TABLE IF NOT EXISTS user_roles (\n id uuid PRIMARY KEY,\n user_id uuid NOT NULL,\n role_id uuid NOT NULL\n);\n\n`;
42
+ for (const role of model.roles) {
43
+ sql += `INSERT INTO m_roles (id, name) VALUES (gen_random_uuid(), '${role}') ON CONFLICT DO NOTHING;\n`;
44
+ }
45
+ sql += '\n';
46
+ }
47
+ // DBファンクション
48
+ if (security.functions) {
49
+ for (const [fnName, fn] of Object.entries(security.functions)) {
50
+ const f = fn;
51
+ const useTemplate = f.use_template !== false; // デフォルトtrue
52
+ const templateType = f.template_type || 'simple';
53
+ let sqlBody = f.sql;
54
+ if (!sqlBody && useTemplate) {
55
+ // template_type優先
56
+ if (templateType) {
57
+ sqlBody = getFunctionTemplate(fnName, templateType, model);
58
+ }
59
+ }
60
+ // fallback: roles定義があればuser_roles参照テンプレ
61
+ if (!sqlBody) {
62
+ if (model.roles && Array.isArray(model.roles)) {
63
+ sqlBody = `CREATE FUNCTION ${fnName}() RETURNS text AS $$\nBEGIN\n -- ログインユーザーのロールを返す(テンプレ)\n RETURN (SELECT r.name FROM user_roles ur JOIN m_roles r ON ur.role_id = r.id WHERE ur.user_id = current_setting('request.jwt.claim.sub', true)::uuid LIMIT 1);\nEND;\n$$ LANGUAGE plpgsql;`;
64
+ }
65
+ else {
66
+ sqlBody = `CREATE FUNCTION ${fnName}() RETURNS text AS $$\nBEGIN\n -- TODO: ロジックを記述\n RETURN 'admin';\nEND;\n$$ LANGUAGE plpgsql;`;
67
+ }
68
+ }
69
+ sql += `-- ${fnName}関数: ユーザーのロール取得\n`;
70
+ sql += `${sqlBody}\n\n`;
71
+ }
72
+ }
73
+ // RLSポリシー
74
+ if (security.policies) {
75
+ for (const [tableName, tablePolicies] of Object.entries(security.policies)) {
76
+ const p = tablePolicies;
77
+ for (const [action, policy] of Object.entries(p)) {
78
+ const pol = policy;
79
+ const useTemplate = pol.use_template !== false; // デフォルトtrue
80
+ const templateType = pol.template_type || 'simple';
81
+ let usingCond = pol.using;
82
+ if (!usingCond && useTemplate && pol.role && Array.isArray(pol.role)) {
83
+ usingCond = getPolicyTemplate(pol.role, templateType);
84
+ }
85
+ if (!usingCond) {
86
+ usingCond = 'true -- TODO: 適切な条件を記述';
87
+ }
88
+ sql += `-- ${tableName}テーブル ${action}用RLSポリシー\n`;
89
+ sql += `ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;\n`;
90
+ sql += `CREATE POLICY ${tableName}_${action}_policy ON ${tableName}\n FOR ${action.toUpperCase()}\n USING (${usingCond});\n\n`;
91
+ }
92
+ }
93
+ }
94
+ fs_1.default.writeFileSync(outPath, sql);
95
+ }
96
+ // テンプレート切り替え関数
97
+ function getFunctionTemplate(fnName, type, model) {
98
+ const templatePath = path_1.default.join(__dirname, '../templates/rls', `function_${type}.sql`);
99
+ let template = fs_1.default.readFileSync(templatePath, 'utf-8');
100
+ template = template.replace(/\$\{functionName\}/g, fnName);
101
+ template = template.replace(/\$\{userIdExpr\}/g, "current_setting('request.jwt.claim.sub', true)::uuid");
102
+ template = template.replace(/\$\{tenantIdExpr\}/g, "current_setting('tenant.id', true)::uuid");
103
+ return template;
104
+ }
105
+ function getPolicyTemplate(roles, type) {
106
+ const templatePath = path_1.default.join(__dirname, '../templates/rls', `policy_${type}.sql`);
107
+ let template = fs_1.default.readFileSync(templatePath, 'utf-8');
108
+ template = template.replace(/\$\{roles\}/g, roles.map(r => `'${r}'`).join(', '));
109
+ template = template.replace(/\$\{tenantIdExpr\}/g, "current_setting('tenant.id', true)::uuid");
110
+ return template;
111
+ }