supatool 0.1.23 → 0.3.1
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.md +260 -38
- package/dist/bin/helptext.js +106 -20
- package/dist/bin/supatool.js +172 -21
- package/dist/generator/client.js +9 -0
- package/dist/generator/crudGenerator.js +194 -0
- package/dist/generator/docGenerator.js +64 -0
- package/dist/generator/rlsGenerator.js +111 -0
- package/dist/generator/schemaCrudGenerator.js +560 -0
- package/dist/generator/sqlGenerator.js +100 -0
- package/dist/generator/typeGenerator.js +55 -0
- package/dist/generator/types.js +2 -0
- package/dist/index.js +123 -37
- package/dist/integrations/supabase/crud-autogen/tasks.js +220 -0
- package/dist/integrations/supabase/crud-autogen/workflows.js +220 -0
- package/dist/parser/modelParser.js +18 -0
- package/dist/sync/config.js +98 -0
- package/dist/sync/definitionExtractor.js +1205 -0
- package/dist/sync/fetchRemoteSchemas.js +369 -0
- package/dist/sync/generateMigration.js +276 -0
- package/dist/sync/index.js +22 -0
- package/dist/sync/migrationRunner.js +271 -0
- package/dist/sync/parseLocalSchemas.js +97 -0
- package/dist/sync/sync.js +208 -0
- package/dist/sync/utils.js +52 -0
- package/dist/sync/writeSchema.js +161 -0
- package/package.json +18 -3
@@ -0,0 +1,55 @@
|
|
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.generateTypesFromModel = generateTypesFromModel;
|
7
|
+
// TypeScript型定義自動生成(最小雛形)
|
8
|
+
// 日本語コメント
|
9
|
+
const path_1 = __importDefault(require("path"));
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
11
|
+
/**
|
12
|
+
* モデルからTypeScript型定義ファイルを生成
|
13
|
+
* @param model モデルオブジェクト
|
14
|
+
* @param outPath 出力先パス
|
15
|
+
*/
|
16
|
+
function generateTypesFromModel(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
|
+
let code = '// 自動生成: モデル型定義\n\n';
|
22
|
+
for (const m of model.models) {
|
23
|
+
const tables = m.tables || {};
|
24
|
+
for (const [tableName, table] of Object.entries(tables)) {
|
25
|
+
const t = table;
|
26
|
+
if (t.skipCreate)
|
27
|
+
continue;
|
28
|
+
code += `export type ${tableName} = {\n`;
|
29
|
+
for (const [colName, col] of Object.entries(t.fields || {})) {
|
30
|
+
const c = col;
|
31
|
+
code += ` ${colName}: ${toTsType(c.type)};\n`;
|
32
|
+
}
|
33
|
+
code += `}\n\n`;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
fs_1.default.writeFileSync(outPath, code);
|
37
|
+
}
|
38
|
+
// 型変換ユーティリティ
|
39
|
+
function toTsType(type) {
|
40
|
+
if (!type)
|
41
|
+
return 'any';
|
42
|
+
switch (type) {
|
43
|
+
case 'uuid':
|
44
|
+
case 'text':
|
45
|
+
case 'timestamp':
|
46
|
+
return 'string';
|
47
|
+
case 'int':
|
48
|
+
case 'integer':
|
49
|
+
return 'number';
|
50
|
+
case 'boolean':
|
51
|
+
return 'boolean';
|
52
|
+
default:
|
53
|
+
return 'any';
|
54
|
+
}
|
55
|
+
}
|
package/dist/index.js
CHANGED
@@ -161,13 +161,13 @@ const toUpperCamelCase = (str) => {
|
|
161
161
|
// CRUD template
|
162
162
|
const crudTemplate = (typeName, fields, isView) => {
|
163
163
|
const upperCamelTypeName = toUpperCamelCase(typeName);
|
164
|
-
const
|
165
|
-
const
|
166
|
-
const
|
167
|
-
const
|
164
|
+
const getByIdFunctionName = 'select' + upperCamelTypeName + 'RowById';
|
165
|
+
const getByFiltersFunctionName = 'select' + upperCamelTypeName + 'RowsWithFilters';
|
166
|
+
const getSingleByFiltersFunctionName = 'select' + upperCamelTypeName + 'SingleRowWithFilters';
|
167
|
+
const createFunctionName = 'insert' + upperCamelTypeName + 'Row';
|
168
168
|
const updateFunctionName = 'update' + upperCamelTypeName + 'Row';
|
169
169
|
const deleteFunctionName = 'delete' + upperCamelTypeName + 'Row';
|
170
|
-
const idType = fields.find((field) => field.name === 'id')?.type;
|
170
|
+
const idType = fields.find((field) => field.name === 'id')?.type || 'string';
|
171
171
|
const hasIdColumn = idType !== undefined; // Check if 'id' column exists
|
172
172
|
const exportHeaders = `// Supabase CRUD operations for ${typeName}
|
173
173
|
// This file is automatically generated. Do not edit it directly.
|
@@ -180,17 +180,29 @@ type Filters = Record<string, FilterTypesValue | FilterTypesValue[]>;
|
|
180
180
|
`;
|
181
181
|
const exportSelectById = `
|
182
182
|
// Read single row using id
|
183
|
-
async function ${
|
184
|
-
if (id
|
183
|
+
export async function ${getByIdFunctionName}({ id }: { id: ${idType} }): Promise<${typeName} | null> {
|
184
|
+
if (!id) {
|
185
|
+
throw new Error('ID is required');
|
186
|
+
}
|
187
|
+
try {
|
185
188
|
const result = await supabase
|
186
189
|
.from('${typeName.toLowerCase()}')
|
187
190
|
.select('*')
|
188
191
|
.eq('id', id)
|
189
192
|
.single();
|
193
|
+
|
194
|
+
if (result.error) {
|
195
|
+
if (result.error.code === 'PGRST116') {
|
196
|
+
return null;
|
197
|
+
}
|
198
|
+
throw new Error(\`Failed to fetch ${typeName}: \${result.error.message}\`);
|
199
|
+
}
|
200
|
+
|
190
201
|
return result.data as ${typeName};
|
202
|
+
} catch (error) {
|
203
|
+
console.error('Error in ${getByIdFunctionName}:', error);
|
204
|
+
throw error;
|
191
205
|
}
|
192
|
-
const result = await supabase.from('${typeName.toLowerCase()}').select('*');
|
193
|
-
return result.data as ${typeName}[];
|
194
206
|
}
|
195
207
|
`;
|
196
208
|
const exportSelectQueries = `
|
@@ -248,64 +260,138 @@ function applyFilters(query: any, filters: Filters): any {
|
|
248
260
|
}
|
249
261
|
|
250
262
|
// Read multiple rows with dynamic filters
|
251
|
-
async function ${
|
252
|
-
|
253
|
-
|
263
|
+
export async function ${getByFiltersFunctionName}({ filters }: { filters: Filters }): Promise<${typeName}[]> {
|
264
|
+
try {
|
265
|
+
let query = supabase.from('${typeName.toLowerCase()}').select('*');
|
266
|
+
query = applyFilters(query, filters);
|
254
267
|
|
255
|
-
|
256
|
-
|
268
|
+
const result = await query;
|
269
|
+
|
270
|
+
if (result.error) {
|
271
|
+
throw new Error(\`Failed to fetch ${typeName}: \${result.error.message}\`);
|
272
|
+
}
|
273
|
+
|
274
|
+
return (result.data as unknown as ${typeName}[]) || [];
|
275
|
+
} catch (error) {
|
276
|
+
console.error('Error in ${getByFiltersFunctionName}:', error);
|
277
|
+
throw error;
|
278
|
+
}
|
257
279
|
}
|
258
280
|
|
259
281
|
// Read a single row with dynamic filters
|
260
|
-
async function ${
|
261
|
-
|
262
|
-
|
282
|
+
export async function ${getSingleByFiltersFunctionName}({ filters }: { filters: Filters }): Promise<${typeName} | null> {
|
283
|
+
try {
|
284
|
+
let query = supabase.from('${typeName.toLowerCase()}').select('*');
|
285
|
+
query = applyFilters(query, filters).single();
|
263
286
|
|
264
|
-
|
265
|
-
|
287
|
+
const result = await query;
|
288
|
+
|
289
|
+
if (result.error) {
|
290
|
+
if (result.error.code === 'PGRST116') {
|
291
|
+
return null;
|
292
|
+
}
|
293
|
+
throw new Error(\`Failed to fetch ${typeName}: \${result.error.message}\`);
|
294
|
+
}
|
295
|
+
|
296
|
+
return result.data as unknown as ${typeName};
|
297
|
+
} catch (error) {
|
298
|
+
console.error('Error in ${getSingleByFiltersFunctionName}:', error);
|
299
|
+
throw error;
|
300
|
+
}
|
266
301
|
}
|
267
302
|
`;
|
268
303
|
const exportInsertOperation = isView ? '' :
|
269
304
|
`
|
270
305
|
// Create Function
|
271
|
-
async function ${
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
306
|
+
export async function ${createFunctionName}({ data }: { data: TablesInsert<'${typeName}'> }): Promise<${typeName}> {
|
307
|
+
if (!data) {
|
308
|
+
throw new Error('Data is required for creation');
|
309
|
+
}
|
310
|
+
try {
|
311
|
+
const result = await supabase
|
312
|
+
.from('${typeName}')
|
313
|
+
.insert([data])
|
314
|
+
.select()
|
315
|
+
.single();
|
277
316
|
|
278
|
-
|
317
|
+
if (result.error) {
|
318
|
+
throw new Error(\`Failed to create ${typeName}: \${result.error.message}\`);
|
319
|
+
}
|
320
|
+
|
321
|
+
if (!result.data) {
|
322
|
+
throw new Error('No data returned after creation');
|
323
|
+
}
|
324
|
+
|
279
325
|
return result.data as ${typeName};
|
326
|
+
} catch (error) {
|
327
|
+
console.error('Error in ${createFunctionName}:', error);
|
328
|
+
throw error;
|
280
329
|
}
|
281
|
-
throw new Error('Failed to insert data');
|
282
330
|
}
|
283
331
|
`;
|
284
332
|
const exportUpdateOperation = isView ? '' :
|
285
333
|
`
|
286
334
|
// Update Function
|
287
|
-
async function ${updateFunctionName}(data
|
288
|
-
|
289
|
-
|
335
|
+
export async function ${updateFunctionName}({ id, data }: { id: ${idType}; data: TablesUpdate<'${typeName}'> }): Promise<${typeName}> {
|
336
|
+
if (!id) {
|
337
|
+
throw new Error('ID is required for update');
|
338
|
+
}
|
339
|
+
if (!data || Object.keys(data).length === 0) {
|
340
|
+
throw new Error('Update data is required');
|
341
|
+
}
|
342
|
+
try {
|
343
|
+
const result = await supabase
|
344
|
+
.from('${typeName.toLowerCase()}')
|
345
|
+
.update(data)
|
346
|
+
.eq('id', id)
|
347
|
+
.select()
|
348
|
+
.single();
|
349
|
+
|
350
|
+
if (result.error) {
|
351
|
+
if (result.error.code === 'PGRST116') {
|
352
|
+
throw new Error(\`${typeName} with ID \${id} not found\`);
|
353
|
+
}
|
354
|
+
throw new Error(\`Failed to update ${typeName}: \${result.error.message}\`);
|
355
|
+
}
|
356
|
+
|
357
|
+
if (!result.data) {
|
358
|
+
throw new Error(\`${typeName} with ID \${id} not found\`);
|
359
|
+
}
|
360
|
+
|
290
361
|
return result.data as ${typeName};
|
362
|
+
} catch (error) {
|
363
|
+
console.error('Error in ${updateFunctionName}:', error);
|
364
|
+
throw error;
|
291
365
|
}
|
292
|
-
throw new Error('Failed to update data');
|
293
366
|
}
|
294
367
|
`;
|
295
368
|
const exportDeleteOperation = isView ? '' :
|
296
369
|
`
|
297
370
|
// Delete Function
|
298
|
-
async function ${deleteFunctionName}(id: ${idType}): Promise
|
299
|
-
|
300
|
-
|
301
|
-
|
371
|
+
export async function ${deleteFunctionName}({ id }: { id: ${idType} }): Promise<boolean> {
|
372
|
+
if (!id) {
|
373
|
+
throw new Error('ID is required for deletion');
|
374
|
+
}
|
375
|
+
try {
|
376
|
+
const result = await supabase
|
377
|
+
.from('${typeName.toLowerCase()}')
|
378
|
+
.delete()
|
379
|
+
.eq('id', id);
|
380
|
+
|
381
|
+
if (result.error) {
|
382
|
+
throw new Error(\`Failed to delete ${typeName}: \${result.error.message}\`);
|
383
|
+
}
|
384
|
+
|
385
|
+
return true;
|
386
|
+
} catch (error) {
|
387
|
+
console.error('Error in ${deleteFunctionName}:', error);
|
388
|
+
throw error;
|
302
389
|
}
|
303
|
-
throw new Error('Failed to delete data');
|
304
390
|
}
|
305
391
|
`;
|
306
392
|
// Export all functions
|
307
393
|
const exportAll = `
|
308
|
-
|
394
|
+
// All functions are exported individually above
|
309
395
|
`;
|
310
396
|
// Return all the code
|
311
397
|
return exportHeaders + exportSelectQueries + (hasIdColumn ? exportSelectById : '') + exportInsertOperation + exportUpdateOperation + exportDeleteOperation + exportAll;
|
@@ -0,0 +1,220 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.getTasksByFilters = getTasksByFilters;
|
4
|
+
exports.getTasksSingleByFilters = getTasksSingleByFilters;
|
5
|
+
exports.getTasksById = getTasksById;
|
6
|
+
exports.createTasks = createTasks;
|
7
|
+
exports.updateTasks = updateTasks;
|
8
|
+
exports.deleteTasks = deleteTasks;
|
9
|
+
exports.queryTasks = queryTasks;
|
10
|
+
// Supabase CRUD operations for tasks
|
11
|
+
// This file is automatically generated. Do not edit it directly.
|
12
|
+
const client_1 = require("../client");
|
13
|
+
// Function to apply filters to a query
|
14
|
+
function applyFilters(query, filters) {
|
15
|
+
for (const [key, value] of Object.entries(filters)) {
|
16
|
+
if (Array.isArray(value)) {
|
17
|
+
query = query.in(key, value); // Use 'in' for array values
|
18
|
+
}
|
19
|
+
else if (typeof value === 'object' && value !== null) {
|
20
|
+
for (const [operator, val] of Object.entries(value)) {
|
21
|
+
switch (operator) {
|
22
|
+
case 'eq':
|
23
|
+
query = query.eq(key, val);
|
24
|
+
break;
|
25
|
+
case 'neq':
|
26
|
+
query = query.neq(key, val);
|
27
|
+
break;
|
28
|
+
case 'like':
|
29
|
+
query = query.like(key, val);
|
30
|
+
break;
|
31
|
+
case 'ilike':
|
32
|
+
query = query.ilike(key, val);
|
33
|
+
break;
|
34
|
+
case 'lt':
|
35
|
+
query = query.lt(key, val);
|
36
|
+
break;
|
37
|
+
case 'lte':
|
38
|
+
query = query.lte(key, val);
|
39
|
+
break;
|
40
|
+
case 'gte':
|
41
|
+
query = query.gte(key, val);
|
42
|
+
break;
|
43
|
+
case 'gt':
|
44
|
+
query = query.gt(key, val);
|
45
|
+
break;
|
46
|
+
case 'contains':
|
47
|
+
query = query.contains(key, val);
|
48
|
+
break;
|
49
|
+
case 'contains_any':
|
50
|
+
query = query.contains_any(key, val);
|
51
|
+
break;
|
52
|
+
case 'contains_all':
|
53
|
+
query = query.contains_all(key, val);
|
54
|
+
break;
|
55
|
+
// Add more operators as needed
|
56
|
+
default:
|
57
|
+
throw new Error('Unsupported operator: ' + operator);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
}
|
61
|
+
else {
|
62
|
+
query = query.eq(key, value); // Default to 'eq' for simple values
|
63
|
+
}
|
64
|
+
}
|
65
|
+
return query;
|
66
|
+
}
|
67
|
+
// Read multiple rows with dynamic filters
|
68
|
+
async function getTasksByFilters({ filters }) {
|
69
|
+
try {
|
70
|
+
let query = client_1.supabase.from('tasks').select('*');
|
71
|
+
query = applyFilters(query, filters);
|
72
|
+
const result = await query;
|
73
|
+
if (result.error) {
|
74
|
+
throw new Error(`Failed to fetch tasks: ${result.error.message}`);
|
75
|
+
}
|
76
|
+
return result.data || [];
|
77
|
+
}
|
78
|
+
catch (error) {
|
79
|
+
console.error('Error in getTasksByFilters:', error);
|
80
|
+
throw error;
|
81
|
+
}
|
82
|
+
}
|
83
|
+
// Read a single row with dynamic filters
|
84
|
+
async function getTasksSingleByFilters({ filters }) {
|
85
|
+
try {
|
86
|
+
let query = client_1.supabase.from('tasks').select('*');
|
87
|
+
query = applyFilters(query, filters).single();
|
88
|
+
const result = await query;
|
89
|
+
if (result.error) {
|
90
|
+
if (result.error.code === 'PGRST116') {
|
91
|
+
return null;
|
92
|
+
}
|
93
|
+
throw new Error(`Failed to fetch tasks: ${result.error.message}`);
|
94
|
+
}
|
95
|
+
return result.data;
|
96
|
+
}
|
97
|
+
catch (error) {
|
98
|
+
console.error('Error in getTasksSingleByFilters:', error);
|
99
|
+
throw error;
|
100
|
+
}
|
101
|
+
}
|
102
|
+
// Read single row using id
|
103
|
+
async function getTasksById({ id }) {
|
104
|
+
if (!id) {
|
105
|
+
throw new Error('ID is required');
|
106
|
+
}
|
107
|
+
try {
|
108
|
+
const result = await client_1.supabase
|
109
|
+
.from('tasks')
|
110
|
+
.select('*')
|
111
|
+
.eq('id', id)
|
112
|
+
.single();
|
113
|
+
if (result.error) {
|
114
|
+
if (result.error.code === 'PGRST116') {
|
115
|
+
return null;
|
116
|
+
}
|
117
|
+
throw new Error(`Failed to fetch tasks: ${result.error.message}`);
|
118
|
+
}
|
119
|
+
return result.data;
|
120
|
+
}
|
121
|
+
catch (error) {
|
122
|
+
console.error('Error in getTasksById:', error);
|
123
|
+
throw error;
|
124
|
+
}
|
125
|
+
}
|
126
|
+
// Create Function
|
127
|
+
async function createTasks({ data }) {
|
128
|
+
if (!data) {
|
129
|
+
throw new Error('Data is required for creation');
|
130
|
+
}
|
131
|
+
try {
|
132
|
+
const result = await client_1.supabase
|
133
|
+
.from('tasks')
|
134
|
+
.insert([data])
|
135
|
+
.select()
|
136
|
+
.single();
|
137
|
+
if (result.error) {
|
138
|
+
throw new Error(`Failed to create tasks: ${result.error.message}`);
|
139
|
+
}
|
140
|
+
if (!result.data) {
|
141
|
+
throw new Error('No data returned after creation');
|
142
|
+
}
|
143
|
+
return result.data;
|
144
|
+
}
|
145
|
+
catch (error) {
|
146
|
+
console.error('Error in createTasks:', error);
|
147
|
+
throw error;
|
148
|
+
}
|
149
|
+
}
|
150
|
+
// Update Function
|
151
|
+
async function updateTasks({ id, data }) {
|
152
|
+
if (!id) {
|
153
|
+
throw new Error('ID is required for update');
|
154
|
+
}
|
155
|
+
if (!data || Object.keys(data).length === 0) {
|
156
|
+
throw new Error('Update data is required');
|
157
|
+
}
|
158
|
+
try {
|
159
|
+
const result = await client_1.supabase
|
160
|
+
.from('tasks')
|
161
|
+
.update(data)
|
162
|
+
.eq('id', id)
|
163
|
+
.select()
|
164
|
+
.single();
|
165
|
+
if (result.error) {
|
166
|
+
if (result.error.code === 'PGRST116') {
|
167
|
+
throw new Error(`tasks with ID ${id} not found`);
|
168
|
+
}
|
169
|
+
throw new Error(`Failed to update tasks: ${result.error.message}`);
|
170
|
+
}
|
171
|
+
if (!result.data) {
|
172
|
+
throw new Error(`tasks with ID ${id} not found`);
|
173
|
+
}
|
174
|
+
return result.data;
|
175
|
+
}
|
176
|
+
catch (error) {
|
177
|
+
console.error('Error in updateTasks:', error);
|
178
|
+
throw error;
|
179
|
+
}
|
180
|
+
}
|
181
|
+
// Delete Function
|
182
|
+
async function deleteTasks({ id }) {
|
183
|
+
if (!id) {
|
184
|
+
throw new Error('ID is required for deletion');
|
185
|
+
}
|
186
|
+
try {
|
187
|
+
const result = await client_1.supabase
|
188
|
+
.from('tasks')
|
189
|
+
.delete()
|
190
|
+
.eq('id', id);
|
191
|
+
if (result.error) {
|
192
|
+
throw new Error(`Failed to delete tasks: ${result.error.message}`);
|
193
|
+
}
|
194
|
+
return true;
|
195
|
+
}
|
196
|
+
catch (error) {
|
197
|
+
console.error('Error in deleteTasks:', error);
|
198
|
+
throw error;
|
199
|
+
}
|
200
|
+
}
|
201
|
+
// Custom query function
|
202
|
+
async function queryTasks({ query }) {
|
203
|
+
if (!query) {
|
204
|
+
throw new Error('Query is required');
|
205
|
+
}
|
206
|
+
try {
|
207
|
+
const result = await client_1.supabase
|
208
|
+
.from('tasks')
|
209
|
+
.select(query);
|
210
|
+
if (result.error) {
|
211
|
+
throw new Error(`Failed to execute query: ${result.error.message}`);
|
212
|
+
}
|
213
|
+
return result.data || [];
|
214
|
+
}
|
215
|
+
catch (error) {
|
216
|
+
console.error('Error in queryTasks:', error);
|
217
|
+
throw error;
|
218
|
+
}
|
219
|
+
}
|
220
|
+
// All functions are exported individually above
|