supatool 0.1.22 → 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,560 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.generateCrudFromSchemas = generateCrudFromSchemas;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ /**
40
+ * SQLファイルからテーブル情報を解析
41
+ */
42
+ function parseTableFromSQL(sqlContent, tableName) {
43
+ // CREATE VIEWかCREATE TABLEかを判定
44
+ const isView = /CREATE\s+(OR\s+REPLACE\s+)?VIEW/i.test(sqlContent);
45
+ let createMatch;
46
+ if (isView) {
47
+ // CREATE VIEW部分を抽出 - SELECTクエリから列情報を推測
48
+ createMatch = sqlContent.match(/CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\s+[^(]*\s+AS\s+SELECT\s+(.*?)\s+FROM/is);
49
+ if (!createMatch) {
50
+ // 簡単な列推測のため、デフォルトの列を返す
51
+ return {
52
+ name: tableName,
53
+ columns: [
54
+ { name: 'id', type: 'UUID', nullable: false },
55
+ ],
56
+ isView: true
57
+ };
58
+ }
59
+ }
60
+ else {
61
+ // CREATE TABLE部分を抽出
62
+ createMatch = sqlContent.match(/CREATE TABLE[^(]*\((.*?)\);?/is);
63
+ if (!createMatch) {
64
+ return null;
65
+ }
66
+ }
67
+ if (isView) {
68
+ // ビューの場合、SELECTされている列を解析(簡略化)
69
+ const selectColumns = createMatch[1];
70
+ const columns = [];
71
+ // 簡単な列解析(カンマ区切り)
72
+ const columnParts = selectColumns.split(',').map(part => part.trim());
73
+ for (const part of columnParts) {
74
+ // ASエイリアスを考慮
75
+ const aliasMatch = part.match(/\s+AS\s+([a-zA-Z_][a-zA-Z0-9_]*)/i);
76
+ let columnName;
77
+ if (aliasMatch) {
78
+ columnName = aliasMatch[1];
79
+ }
80
+ else {
81
+ // ドット記法やシンプルな列名を抽出
82
+ const simpleMatch = part.match(/([a-zA-Z_][a-zA-Z0-9_]*)$/);
83
+ columnName = simpleMatch ? simpleMatch[1] : `column_${columns.length + 1}`;
84
+ }
85
+ columns.push({
86
+ name: columnName,
87
+ type: 'TEXT', // ビューの場合は型推測が困難なのでデフォルト
88
+ nullable: true
89
+ });
90
+ }
91
+ return {
92
+ name: tableName,
93
+ columns: columns.length > 0 ? columns : [
94
+ { name: 'id', type: 'UUID', nullable: false }
95
+ ],
96
+ isView: true
97
+ };
98
+ }
99
+ const tableContent = createMatch[1];
100
+ const columns = [];
101
+ // カンマで分割して各部分を解析
102
+ const parts = tableContent.split(',').map(part => part.trim());
103
+ for (const part of parts) {
104
+ // CONSTRAINTやPRIMARY KEYは除外
105
+ if (part.match(/^(PRIMARY|CONSTRAINT|UNIQUE|FOREIGN|CHECK)/i)) {
106
+ continue;
107
+ }
108
+ // カラム定義を解析
109
+ const columnMatch = part.trim().match(/^([a-zA-Z_][a-zA-Z0-9_]*)\s+(.+)$/);
110
+ if (columnMatch) {
111
+ const columnName = columnMatch[1];
112
+ const columnDef = columnMatch[2];
113
+ // データ型を抽出
114
+ const typeMatch = columnDef.match(/^(\w+(?:\([^)]*\))?)/);
115
+ const type = typeMatch ? typeMatch[1] : 'TEXT';
116
+ // NULL制約チェック
117
+ const nullable = !columnDef.includes('NOT NULL');
118
+ // デフォルト値を抽出
119
+ const defaultMatch = columnDef.match(/DEFAULT\s+([^,\s]+)/i);
120
+ const defaultValue = defaultMatch ? defaultMatch[1] : undefined;
121
+ columns.push({
122
+ name: columnName,
123
+ type,
124
+ nullable,
125
+ defaultValue
126
+ });
127
+ }
128
+ }
129
+ return {
130
+ name: tableName,
131
+ columns,
132
+ isView: false
133
+ };
134
+ }
135
+ /**
136
+ * PostgreSQL型からTypeScript型への変換
137
+ */
138
+ function mapPostgresToTypescript(pgType) {
139
+ const typeMap = {
140
+ 'UUID': 'string',
141
+ 'TEXT': 'string',
142
+ 'VARCHAR': 'string',
143
+ 'CHAR': 'string',
144
+ 'INTEGER': 'number',
145
+ 'BIGINT': 'number',
146
+ 'SMALLINT': 'number',
147
+ 'REAL': 'number',
148
+ 'DOUBLE': 'number',
149
+ 'NUMERIC': 'number',
150
+ 'BOOLEAN': 'boolean',
151
+ 'TIMESTAMP': 'string',
152
+ 'TIMESTAMPTZ': 'string',
153
+ 'DATE': 'string',
154
+ 'TIME': 'string',
155
+ 'JSON': 'any',
156
+ 'JSONB': 'any'
157
+ };
158
+ // 型名の正規化(括弧やパラメータを除去)
159
+ const normalizedType = pgType.replace(/\([^)]*\)/g, '').toUpperCase();
160
+ return typeMap[normalizedType] || 'any';
161
+ }
162
+ /**
163
+ * TypeScript型定義を生成
164
+ */
165
+ function generateTypeDefinition(table) {
166
+ let typeDef = `export interface ${capitalize(table.name)} {\n`;
167
+ for (const column of table.columns) {
168
+ const tsType = mapPostgresToTypescript(column.type);
169
+ const optional = column.nullable ? '?' : '';
170
+ typeDef += ` ${column.name}${optional}: ${tsType};\n`;
171
+ }
172
+ typeDef += '}\n\n';
173
+ // Insert用の型(通常はIDやタイムスタンプを除外)
174
+ typeDef += `export interface ${capitalize(table.name)}Insert {\n`;
175
+ for (const column of table.columns) {
176
+ // 自動生成されるカラムはオプショナル
177
+ const isAutoGenerated = column.name === 'id' ||
178
+ column.name.includes('created_at') ||
179
+ column.name.includes('updated_at') ||
180
+ column.defaultValue !== undefined;
181
+ const tsType = mapPostgresToTypescript(column.type);
182
+ const optional = isAutoGenerated || column.nullable ? '?' : '';
183
+ typeDef += ` ${column.name}${optional}: ${tsType};\n`;
184
+ }
185
+ typeDef += '}\n\n';
186
+ // Update用の型(全フィールドオプショナル)
187
+ typeDef += `export interface ${capitalize(table.name)}Update {\n`;
188
+ for (const column of table.columns) {
189
+ const tsType = mapPostgresToTypescript(column.type);
190
+ typeDef += ` ${column.name}?: ${tsType};\n`;
191
+ }
192
+ typeDef += '}\n';
193
+ return typeDef;
194
+ }
195
+ /**
196
+ * エラー処理付きCRUD関数を生成
197
+ */
198
+ function generateCrudFunctions(table, withReactQuery = false, withTypes = true, includeWrite = true) {
199
+ const tableName = table.name;
200
+ const TypeName = capitalize(tableName);
201
+ const isView = table.isView || false;
202
+ let code = `// 自動生成: ${tableName}${isView ? 'ビュー' : 'テーブル'}用CRUD関数\n\n`;
203
+ // インポート
204
+ code += `import { supabase } from '../lib/supabase';\n`;
205
+ if (withTypes) {
206
+ code += `import type { ${TypeName}`;
207
+ if (includeWrite && !isView) {
208
+ code += `, ${TypeName}Insert, ${TypeName}Update`;
209
+ }
210
+ code += ` } from '../types/${tableName}';\n\n`;
211
+ }
212
+ else {
213
+ // Supabase CLI生成の型を使用
214
+ code += `import type { Database } from '../types/database';\n\n`;
215
+ if (isView) {
216
+ code += `type ${TypeName} = Database['public']['Views']['${tableName}']['Row'];\n`;
217
+ }
218
+ else {
219
+ code += `type ${TypeName} = Database['public']['Tables']['${tableName}']['Row'];\n`;
220
+ if (includeWrite) {
221
+ code += `type ${TypeName}Insert = Database['public']['Tables']['${tableName}']['Insert'];\n`;
222
+ code += `type ${TypeName}Update = Database['public']['Tables']['${tableName}']['Update'];\n`;
223
+ }
224
+ }
225
+ code += '\n';
226
+ }
227
+ if (withReactQuery) {
228
+ code += `import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';\n\n`;
229
+ }
230
+ // エラー型定義
231
+ code += `export interface DatabaseError {\n`;
232
+ code += ` message: string;\n`;
233
+ code += ` code?: string;\n`;
234
+ code += ` details?: string;\n`;
235
+ code += `}\n\n`;
236
+ code += `export interface CrudResult<T> {\n`;
237
+ code += ` data: T | null;\n`;
238
+ code += ` error: DatabaseError | null;\n`;
239
+ code += ` success: boolean;\n`;
240
+ code += `}\n\n`;
241
+ // 主キーカラムを特定
242
+ const primaryKey = table.columns.find(col => col.name === 'id')?.name || 'id';
243
+ // READ操作
244
+ code += `/**\n * 全件取得\n */\n`;
245
+ code += `export async function getAll${TypeName}(): Promise<CrudResult<${TypeName}[]>> {\n`;
246
+ code += ` try {\n`;
247
+ code += ` const { data, error } = await supabase\n`;
248
+ code += ` .from('${tableName}')\n`;
249
+ code += ` .select('*')\n`;
250
+ code += ` .order('created_at', { ascending: false });\n\n`;
251
+ code += ` if (error) {\n`;
252
+ code += ` return {\n`;
253
+ code += ` data: null,\n`;
254
+ code += ` error: { message: error.message, code: error.code, details: error.details },\n`;
255
+ code += ` success: false\n`;
256
+ code += ` };\n`;
257
+ code += ` }\n\n`;
258
+ code += ` return {\n`;
259
+ code += ` data: data as ${TypeName}[],\n`;
260
+ code += ` error: null,\n`;
261
+ code += ` success: true\n`;
262
+ code += ` };\n`;
263
+ code += ` } catch (error) {\n`;
264
+ code += ` return {\n`;
265
+ code += ` data: null,\n`;
266
+ code += ` error: { message: error instanceof Error ? error.message : '不明なエラー' },\n`;
267
+ code += ` success: false\n`;
268
+ code += ` };\n`;
269
+ code += ` }\n`;
270
+ code += `}\n\n`;
271
+ // IDで取得
272
+ code += `/**\n * IDで1件取得\n */\n`;
273
+ code += `export async function get${TypeName}ById(${primaryKey}: string): Promise<CrudResult<${TypeName}>> {\n`;
274
+ code += ` try {\n`;
275
+ code += ` const { data, error } = await supabase\n`;
276
+ code += ` .from('${tableName}')\n`;
277
+ code += ` .select('*')\n`;
278
+ code += ` .eq('${primaryKey}', ${primaryKey})\n`;
279
+ code += ` .single();\n\n`;
280
+ code += ` if (error) {\n`;
281
+ code += ` return {\n`;
282
+ code += ` data: null,\n`;
283
+ code += ` error: { message: error.message, code: error.code, details: error.details },\n`;
284
+ code += ` success: false\n`;
285
+ code += ` };\n`;
286
+ code += ` }\n\n`;
287
+ code += ` return {\n`;
288
+ code += ` data: data as ${TypeName},\n`;
289
+ code += ` error: null,\n`;
290
+ code += ` success: true\n`;
291
+ code += ` };\n`;
292
+ code += ` } catch (error) {\n`;
293
+ code += ` return {\n`;
294
+ code += ` data: null,\n`;
295
+ code += ` error: { message: error instanceof Error ? error.message : '不明なエラー' },\n`;
296
+ code += ` success: false\n`;
297
+ code += ` };\n`;
298
+ code += ` }\n`;
299
+ code += `}\n\n`;
300
+ // 書き込み操作(CREATE, UPDATE, DELETE)はビューまたはincludeWrite=falseの場合は生成しない
301
+ if (includeWrite && !isView) {
302
+ // CREATE
303
+ code += `/**\n * 新規作成\n */\n`;
304
+ code += `export async function create${TypeName}(data: ${TypeName}Insert): Promise<CrudResult<${TypeName}>> {\n`;
305
+ code += ` try {\n`;
306
+ code += ` const { data: result, error } = await supabase\n`;
307
+ code += ` .from('${tableName}')\n`;
308
+ code += ` .insert(data)\n`;
309
+ code += ` .select()\n`;
310
+ code += ` .single();\n\n`;
311
+ code += ` if (error) {\n`;
312
+ code += ` return {\n`;
313
+ code += ` data: null,\n`;
314
+ code += ` error: { message: error.message, code: error.code, details: error.details },\n`;
315
+ code += ` success: false\n`;
316
+ code += ` };\n`;
317
+ code += ` }\n\n`;
318
+ code += ` return {\n`;
319
+ code += ` data: result as ${TypeName},\n`;
320
+ code += ` error: null,\n`;
321
+ code += ` success: true\n`;
322
+ code += ` };\n`;
323
+ code += ` } catch (error) {\n`;
324
+ code += ` return {\n`;
325
+ code += ` data: null,\n`;
326
+ code += ` error: { message: error instanceof Error ? error.message : '不明なエラー' },\n`;
327
+ code += ` success: false\n`;
328
+ code += ` };\n`;
329
+ code += ` }\n`;
330
+ code += `}\n\n`;
331
+ // UPDATE
332
+ code += `/**\n * 更新\n */\n`;
333
+ code += `export async function update${TypeName}(${primaryKey}: string, data: ${TypeName}Update): Promise<CrudResult<${TypeName}>> {\n`;
334
+ code += ` try {\n`;
335
+ code += ` const { data: result, error } = await supabase\n`;
336
+ code += ` .from('${tableName}')\n`;
337
+ code += ` .update(data)\n`;
338
+ code += ` .eq('${primaryKey}', ${primaryKey})\n`;
339
+ code += ` .select()\n`;
340
+ code += ` .single();\n\n`;
341
+ code += ` if (error) {\n`;
342
+ code += ` return {\n`;
343
+ code += ` data: null,\n`;
344
+ code += ` error: { message: error.message, code: error.code, details: error.details },\n`;
345
+ code += ` success: false\n`;
346
+ code += ` };\n`;
347
+ code += ` }\n\n`;
348
+ code += ` return {\n`;
349
+ code += ` data: result as ${TypeName},\n`;
350
+ code += ` error: null,\n`;
351
+ code += ` success: true\n`;
352
+ code += ` };\n`;
353
+ code += ` } catch (error) {\n`;
354
+ code += ` return {\n`;
355
+ code += ` data: null,\n`;
356
+ code += ` error: { message: error instanceof Error ? error.message : '不明なエラー' },\n`;
357
+ code += ` success: false\n`;
358
+ code += ` };\n`;
359
+ code += ` }\n`;
360
+ code += `}\n\n`;
361
+ // DELETE
362
+ code += `/**\n * 削除\n */\n`;
363
+ code += `export async function delete${TypeName}(${primaryKey}: string): Promise<CrudResult<boolean>> {\n`;
364
+ code += ` try {\n`;
365
+ code += ` const { error } = await supabase\n`;
366
+ code += ` .from('${tableName}')\n`;
367
+ code += ` .delete()\n`;
368
+ code += ` .eq('${primaryKey}', ${primaryKey});\n\n`;
369
+ code += ` if (error) {\n`;
370
+ code += ` return {\n`;
371
+ code += ` data: null,\n`;
372
+ code += ` error: { message: error.message, code: error.code, details: error.details },\n`;
373
+ code += ` success: false\n`;
374
+ code += ` };\n`;
375
+ code += ` }\n\n`;
376
+ code += ` return {\n`;
377
+ code += ` data: true,\n`;
378
+ code += ` error: null,\n`;
379
+ code += ` success: true\n`;
380
+ code += ` };\n`;
381
+ code += ` } catch (error) {\n`;
382
+ code += ` return {\n`;
383
+ code += ` data: null,\n`;
384
+ code += ` error: { message: error instanceof Error ? error.message : '不明なエラー' },\n`;
385
+ code += ` success: false\n`;
386
+ code += ` };\n`;
387
+ code += ` }\n`;
388
+ code += `}\n\n`;
389
+ } // end of includeWrite && !isView
390
+ // React Query hooks
391
+ if (withReactQuery) {
392
+ code += `// React Query hooks\n\n`;
393
+ // クエリキー
394
+ code += `export const ${tableName}QueryKeys = {\n`;
395
+ code += ` all: ['${tableName}'] as const,\n`;
396
+ code += ` lists: () => [...${tableName}QueryKeys.all, 'list'] as const,\n`;
397
+ code += ` list: (filters: string) => [...${tableName}QueryKeys.lists(), { filters }] as const,\n`;
398
+ code += ` details: () => [...${tableName}QueryKeys.all, 'detail'] as const,\n`;
399
+ code += ` detail: (id: string) => [...${tableName}QueryKeys.details(), id] as const,\n`;
400
+ code += `};\n\n`;
401
+ // useQuery hooks
402
+ code += `/**\n * 全件取得のQuery hook\n */\n`;
403
+ code += `export function use${TypeName}List() {\n`;
404
+ code += ` return useQuery({\n`;
405
+ code += ` queryKey: ${tableName}QueryKeys.lists(),\n`;
406
+ code += ` queryFn: async () => {\n`;
407
+ code += ` const result = await getAll${TypeName}();\n`;
408
+ code += ` if (!result.success) {\n`;
409
+ code += ` throw new Error(result.error?.message || '取得に失敗しました');\n`;
410
+ code += ` }\n`;
411
+ code += ` return result.data!;\n`;
412
+ code += ` },\n`;
413
+ code += ` });\n`;
414
+ code += `}\n\n`;
415
+ code += `/**\n * ID指定取得のQuery hook\n */\n`;
416
+ code += `export function use${TypeName}Detail(${primaryKey}: string) {\n`;
417
+ code += ` return useQuery({\n`;
418
+ code += ` queryKey: ${tableName}QueryKeys.detail(${primaryKey}),\n`;
419
+ code += ` queryFn: async () => {\n`;
420
+ code += ` const result = await get${TypeName}ById(${primaryKey});\n`;
421
+ code += ` if (!result.success) {\n`;
422
+ code += ` throw new Error(result.error?.message || '取得に失敗しました');\n`;
423
+ code += ` }\n`;
424
+ code += ` return result.data!;\n`;
425
+ code += ` },\n`;
426
+ code += ` enabled: !!${primaryKey},\n`;
427
+ code += ` });\n`;
428
+ code += `}\n\n`;
429
+ // useMutation hooks(書き込み操作がある場合のみ)
430
+ if (includeWrite && !isView) {
431
+ code += `/**\n * 作成のMutation hook\n */\n`;
432
+ code += `export function useCreate${TypeName}() {\n`;
433
+ code += ` const queryClient = useQueryClient();\n\n`;
434
+ code += ` return useMutation({\n`;
435
+ code += ` mutationFn: (data: ${TypeName}Insert) => create${TypeName}(data),\n`;
436
+ code += ` onSuccess: (result) => {\n`;
437
+ code += ` if (result.success) {\n`;
438
+ code += ` queryClient.invalidateQueries({ queryKey: ${tableName}QueryKeys.lists() });\n`;
439
+ code += ` }\n`;
440
+ code += ` },\n`;
441
+ code += ` });\n`;
442
+ code += `}\n\n`;
443
+ code += `/**\n * 更新のMutation hook\n */\n`;
444
+ code += `export function useUpdate${TypeName}() {\n`;
445
+ code += ` const queryClient = useQueryClient();\n\n`;
446
+ code += ` return useMutation({\n`;
447
+ code += ` mutationFn: ({ ${primaryKey}, data }: { ${primaryKey}: string; data: ${TypeName}Update }) => \n`;
448
+ code += ` update${TypeName}(${primaryKey}, data),\n`;
449
+ code += ` onSuccess: (result, variables) => {\n`;
450
+ code += ` if (result.success) {\n`;
451
+ code += ` queryClient.invalidateQueries({ queryKey: ${tableName}QueryKeys.lists() });\n`;
452
+ code += ` queryClient.invalidateQueries({ queryKey: ${tableName}QueryKeys.detail(variables.${primaryKey}) });\n`;
453
+ code += ` }\n`;
454
+ code += ` },\n`;
455
+ code += ` });\n`;
456
+ code += `}\n\n`;
457
+ code += `/**\n * 削除のMutation hook\n */\n`;
458
+ code += `export function useDelete${TypeName}() {\n`;
459
+ code += ` const queryClient = useQueryClient();\n\n`;
460
+ code += ` return useMutation({\n`;
461
+ code += ` mutationFn: (${primaryKey}: string) => delete${TypeName}(${primaryKey}),\n`;
462
+ code += ` onSuccess: (result, ${primaryKey}) => {\n`;
463
+ code += ` if (result.success) {\n`;
464
+ code += ` queryClient.invalidateQueries({ queryKey: ${tableName}QueryKeys.lists() });\n`;
465
+ code += ` queryClient.removeQueries({ queryKey: ${tableName}QueryKeys.detail(${primaryKey}) });\n`;
466
+ code += ` }\n`;
467
+ code += ` },\n`;
468
+ code += ` });\n`;
469
+ code += `}\n`;
470
+ }
471
+ }
472
+ return code;
473
+ }
474
+ /**
475
+ * スキーマディレクトリからCRUD関数を生成
476
+ */
477
+ async function generateCrudFromSchemas(schemaDir = './supabase/schemas', outputDir = './src/lib/crud', options = {}) {
478
+ const { reactQuery = false, separateTypes = true, generateTypes = true, includeViews = false, includeWrite = true, tables = [] } = options;
479
+ // 出力ディレクトリ作成
480
+ if (!fs.existsSync(outputDir)) {
481
+ fs.mkdirSync(outputDir, { recursive: true });
482
+ }
483
+ if (generateTypes && separateTypes) {
484
+ const typesDir = path.join(outputDir, '../types');
485
+ if (!fs.existsSync(typesDir)) {
486
+ fs.mkdirSync(typesDir, { recursive: true });
487
+ }
488
+ }
489
+ // スキーマファイルを読み込み
490
+ const schemaFiles = fs.readdirSync(schemaDir)
491
+ .filter(file => file.endsWith('.sql'))
492
+ .map(file => file.replace('.sql', ''));
493
+ // 指定されたテーブルのみに限定
494
+ const targetTables = tables.length > 0
495
+ ? schemaFiles.filter(table => tables.includes(table))
496
+ : schemaFiles;
497
+ console.log(`🔧 ${targetTables.length}個のテーブルからCRUD関数を生成中...`);
498
+ for (const tableName of targetTables) {
499
+ const sqlPath = path.join(schemaDir, `${tableName}.sql`);
500
+ if (!fs.existsSync(sqlPath)) {
501
+ console.log(`⚠️ スキーマファイルが見つかりません: ${sqlPath}`);
502
+ continue;
503
+ }
504
+ const sqlContent = fs.readFileSync(sqlPath, 'utf-8');
505
+ const tableInfo = parseTableFromSQL(sqlContent, tableName);
506
+ if (!tableInfo) {
507
+ console.log(`⚠️ テーブル情報を解析できませんでした: ${tableName}`);
508
+ continue;
509
+ }
510
+ // ビューの場合、includeViews=falseなら除外
511
+ if (tableInfo.isView && !includeViews) {
512
+ console.log(`⏭️ ビューをスキップ: ${tableName}`);
513
+ continue;
514
+ }
515
+ // 型定義を生成
516
+ if (generateTypes && separateTypes) {
517
+ const typesDef = generateTypeDefinition(tableInfo);
518
+ const typesPath = path.join(outputDir, '../types', `${tableName}.ts`);
519
+ fs.writeFileSync(typesPath, typesDef);
520
+ }
521
+ // CRUD関数を生成
522
+ const crudCode = generateCrudFunctions(tableInfo, reactQuery, generateTypes, includeWrite);
523
+ const crudPath = path.join(outputDir, `${tableName}.ts`);
524
+ fs.writeFileSync(crudPath, crudCode);
525
+ const typeInfo = tableInfo.isView ? 'ビュー' : 'テーブル';
526
+ const writeInfo = includeWrite && !tableInfo.isView ? '(CRUD操作)' : '(SELECT専用)';
527
+ console.log(`✅ ${tableName}: ${typeInfo}用CRUD関数${writeInfo}とTypeScript型定義を生成`);
528
+ }
529
+ // 統合型定義ファイル(separateTypes=falseの場合)
530
+ if (generateTypes && !separateTypes) {
531
+ let allTypes = '// 自動生成: 全テーブルの型定義\n\n';
532
+ for (const tableName of targetTables) {
533
+ const sqlPath = path.join(schemaDir, `${tableName}.sql`);
534
+ const sqlContent = fs.readFileSync(sqlPath, 'utf-8');
535
+ const tableInfo = parseTableFromSQL(sqlContent, tableName);
536
+ if (tableInfo) {
537
+ allTypes += generateTypeDefinition(tableInfo) + '\n';
538
+ }
539
+ }
540
+ fs.writeFileSync(path.join(outputDir, 'types.ts'), allTypes);
541
+ }
542
+ // インデックスファイル生成
543
+ let indexContent = '// 自動生成: CRUD関数エクスポート\n\n';
544
+ for (const tableName of targetTables) {
545
+ indexContent += `export * from './${tableName}';\n`;
546
+ }
547
+ fs.writeFileSync(path.join(outputDir, 'index.ts'), indexContent);
548
+ console.log(`🎉 CRUD生成完了: ${outputDir}`);
549
+ console.log(` - ${targetTables.length}個のテーブル/ビュー`);
550
+ console.log(` - React Query対応: ${reactQuery ? 'あり' : 'なし'}`);
551
+ console.log(` - 型定義生成: ${generateTypes ? 'あり' : 'なし(Supabase CLI型を使用)'}`);
552
+ console.log(` - ビュー含む: ${includeViews ? 'あり' : 'なし'}`);
553
+ console.log(` - 書き込み操作: ${includeWrite ? 'あり(CRUD)' : 'なし(SELECT専用)'}`);
554
+ if (generateTypes) {
555
+ console.log(` - 型定義分離: ${separateTypes ? 'あり' : 'なし'}`);
556
+ }
557
+ }
558
+ function capitalize(str) {
559
+ return str.charAt(0).toUpperCase() + str.slice(1);
560
+ }
@@ -0,0 +1,100 @@
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.generateSqlFromModel = generateSqlFromModel;
7
+ // SQL自動生成(テーブル・リレーション最小雛形)
8
+ // 日本語コメント
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ /**
12
+ * モデルからテーブル定義・リレーションSQLを生成
13
+ * @param model モデルオブジェクト
14
+ * @param outPath 出力先パス
15
+ */
16
+ function generateSqlFromModel(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 = '-- 自動生成: テーブル・リレーションDDL\n\n';
36
+ for (const tableObj of tables) {
37
+ const t = tableObj;
38
+ const tableName = tableObj.tableName;
39
+ if (t.skipCreate) {
40
+ // auth.usersなど作成不要テーブルはコメントで明示
41
+ sql += `-- [skip] ${tableName}(作成不要: Supabase組み込み等)\n`;
42
+ continue;
43
+ }
44
+ // ユーザーIDカラムの自動命名調整
45
+ const userIdFields = Object.entries(t.fields || {}).filter(([colName, col]) => {
46
+ const c = col;
47
+ return (colName === 'user_id' || (c.ref && c.ref.endsWith('user_profiles.id')));
48
+ });
49
+ let userIdCount = userIdFields.length;
50
+ sql += `CREATE TABLE ${tableName} (\n`;
51
+ const colDefs = [];
52
+ for (const [colName, col] of Object.entries(t.fields || {})) {
53
+ const c = col;
54
+ let actualColName = colName;
55
+ // ユーザーIDカラム命名ルール適用
56
+ if ((colName !== 'user_id') && c.ref && c.ref.endsWith('user_profiles.id')) {
57
+ if (userIdCount === 1) {
58
+ actualColName = 'user_id';
59
+ }
60
+ else {
61
+ // 参照先テーブル名_user_id
62
+ const refTable = c.ref.split('.')[0];
63
+ actualColName = `${refTable}_user_id`;
64
+ }
65
+ }
66
+ let def = ` ${actualColName} ${toSqlType(c.type, actualColName)}`;
67
+ if (c.primary)
68
+ def += ' PRIMARY KEY';
69
+ if (c.notNull)
70
+ def += ' NOT NULL';
71
+ if (c.default)
72
+ def += ` DEFAULT ${c.default}`;
73
+ // 外部キー制約はREFERENCESで付与
74
+ if (c.ref) {
75
+ const refTable = c.ref.split('.')[0];
76
+ def += ` REFERENCES ${refTable}(id)`;
77
+ }
78
+ colDefs.push(def);
79
+ }
80
+ sql += colDefs.join(',\n') + '\n);\n\n';
81
+ // ALTER TABLEによる外部キー制約は出力しない
82
+ sql += '\n';
83
+ }
84
+ fs_1.default.writeFileSync(outPath, sql);
85
+ }
86
+ function toSqlType(type, colName) {
87
+ if (!type)
88
+ return 'text';
89
+ // 時刻列は必ずtimestamptz
90
+ if (type === 'timestamp' || type === 'timestamptz' || colName.endsWith('_at'))
91
+ return 'timestamptz';
92
+ switch (type) {
93
+ case 'uuid': return 'uuid';
94
+ case 'text': return 'text';
95
+ case 'int':
96
+ case 'integer': return 'integer';
97
+ case 'boolean': return 'boolean';
98
+ default: return 'text';
99
+ }
100
+ }