supatool 0.3.5 → 0.3.6

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.
Files changed (2) hide show
  1. package/dist/index.js +249 -270
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -82,36 +82,50 @@ function main() {
82
82
  const typeName = typeAliasDecl.name.text;
83
83
  const typeNode = typeAliasDecl.type;
84
84
  if (typeNode.kind === typescript_1.SyntaxKind.TypeLiteral && typeName === 'Database') {
85
- const schemaName = getSchemaName(typeNode);
86
- const schemaType = typeNode.members.find((member) => member.name && member.name.text === schemaName);
87
- if (schemaType && schemaType.type.kind === typescript_1.SyntaxKind.TypeLiteral) {
88
- const tablesAndViewsType = schemaType.type.members.filter((member) => member.name && (member.name.text === 'Tables' || member.name.text === 'Views'));
89
- return tablesAndViewsType.flatMap((tablesOrViewsType) => {
90
- if (tablesOrViewsType.type.kind === typescript_1.SyntaxKind.TypeLiteral) {
91
- return tablesOrViewsType.type.members.map((tableOrViewMember) => {
92
- const tableName = tableOrViewMember.name.text;
93
- const isView = tablesOrViewsType.name.text === 'Views';
94
- const rowType = tableOrViewMember.type.members.find((member) => member.name && member.name.text === 'Row');
95
- if (rowType && rowType.type.kind === typescript_1.SyntaxKind.TypeLiteral) {
96
- const fields = rowType.type.members.map((member) => {
97
- if (member.name && member.name.kind === typescript_1.SyntaxKind.Identifier) {
98
- const name = member.name.getText(sourceFile);
99
- const type = member.type ? member.type.getText(sourceFile) : 'unknown';
100
- return { name, type };
101
- }
102
- return { name: 'unknown', type: 'unknown' };
103
- });
104
- return { typeName: tableName, fields, isView };
105
- }
106
- return null;
107
- }).filter((type) => type !== null);
108
- }
109
- return [];
110
- });
111
- }
85
+ console.log('Found Database type, processing schemas...');
86
+ // Database型内の全てのスキーマを処理
87
+ return typeNode.members.flatMap((schemaMember) => {
88
+ if (schemaMember.name && schemaMember.type && schemaMember.type.kind === typescript_1.SyntaxKind.TypeLiteral) {
89
+ const schemaName = schemaMember.name.text;
90
+ console.log(`Processing schema: ${schemaName}`);
91
+ const schemaType = schemaMember.type;
92
+ // スキーマ内のTablesとViewsを処理
93
+ const tablesAndViewsType = schemaType.members.filter((member) => member.name && (member.name.text === 'Tables' || member.name.text === 'Views'));
94
+ return tablesAndViewsType.flatMap((tablesOrViewsType) => {
95
+ if (tablesOrViewsType.type.kind === typescript_1.SyntaxKind.TypeLiteral) {
96
+ const tableCount = tablesOrViewsType.type.members.length;
97
+ console.log(`Found ${tableCount} ${tablesOrViewsType.name.text} in schema ${schemaName}`);
98
+ return tablesOrViewsType.type.members.map((tableOrViewMember) => {
99
+ const tableName = tableOrViewMember.name.text;
100
+ const isView = tablesOrViewsType.name.text === 'Views';
101
+ console.log(`Processing ${isView ? 'view' : 'table'}: ${tableName}`);
102
+ const rowType = tableOrViewMember.type.members.find((member) => member.name && member.name.text === 'Row');
103
+ if (rowType && rowType.type.kind === typescript_1.SyntaxKind.TypeLiteral) {
104
+ const fields = rowType.type.members.map((member) => {
105
+ if (member.name && member.name.kind === typescript_1.SyntaxKind.Identifier) {
106
+ const name = member.name.getText(sourceFile);
107
+ const type = member.type ? member.type.getText(sourceFile) : 'unknown';
108
+ return { name, type };
109
+ }
110
+ return { name: 'unknown', type: 'unknown' };
111
+ });
112
+ return { typeName: tableName, fields, isView, schema: schemaName };
113
+ }
114
+ return null;
115
+ }).filter((type) => type !== null);
116
+ }
117
+ return [];
118
+ });
119
+ }
120
+ return [];
121
+ });
112
122
  }
113
123
  return [];
114
124
  });
125
+ console.log(`Total types found: ${types.length}`);
126
+ types.forEach(type => {
127
+ console.log(`- ${type.schema}.${type.typeName} (${type.isView ? 'view' : 'table'}) with ${type.fields.length} fields`);
128
+ });
115
129
  // Show start of generation process
116
130
  console.log(`Import path: ${importPath}`);
117
131
  console.log(`Export path: ${exportPath}`);
@@ -129,23 +143,25 @@ function main() {
129
143
  return true;
130
144
  })
131
145
  .forEach(type => {
146
+ // スキーマごとにフォルダ分け
147
+ const schemaFolder = path_1.default.join(crudFolderPath, type.schema);
148
+ if (!(0, fs_1.existsSync)(schemaFolder)) {
149
+ (0, fs_1.mkdirSync)(schemaFolder, { recursive: true });
150
+ }
132
151
  const fileName = toLowerCamelCase(type.typeName);
133
- const crudCode = crudTemplate(type.typeName, type.fields, type.isView);
134
- const filePath = crudFolderPath + `${fileName}.ts`;
152
+ // スキーマフォルダ分けがある場合のインポートパス調整
153
+ const hasSchemaFolders = types.some(t => t.schema !== type.schema);
154
+ const crudCode = crudTemplate(type.typeName, type.fields, type.isView, type.schema, hasSchemaFolders);
155
+ const filePath = path_1.default.join(schemaFolder, `${fileName}.ts`);
135
156
  // Show in console
136
157
  if (type.isView) {
137
- console.log(`Generating select operations only for view: ${fileName}`);
158
+ console.log(`Generating select operations only for view: ${type.schema}/${fileName}`);
138
159
  }
139
160
  else {
140
- console.log(`Generating full CRUD operations for table: ${fileName}`);
141
- }
142
- // Create directory if it does not exist
143
- const dirPath = filePath.substring(0, filePath.lastIndexOf('/'));
144
- if (!(0, fs_1.existsSync)(dirPath)) {
145
- (0, fs_1.mkdirSync)(dirPath, { recursive: true });
161
+ console.log(`Generating full CRUD operations for table: ${type.schema}/${fileName}`);
146
162
  }
147
163
  (0, fs_1.writeFileSync)(filePath, crudCode);
148
- console.log(`Generated ${fileName}.ts`);
164
+ console.log(`Generated ${type.schema}/${fileName}.ts`);
149
165
  });
150
166
  console.log("CRUD operations have been generated.");
151
167
  }
@@ -158,8 +174,8 @@ const toLowerCamelCase = (str) => {
158
174
  const toUpperCamelCase = (str) => {
159
175
  return str.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');
160
176
  };
161
- // CRUD template
162
- const crudTemplate = (typeName, fields, isView) => {
177
+ // CRUDテンプレート本体 - エレガントな文字列生成
178
+ const crudTemplate = (typeName, fields, isView, schema, hasSchemaFolders) => {
163
179
  const upperCamelTypeName = toUpperCamelCase(typeName);
164
180
  const getByIdFunctionName = 'select' + upperCamelTypeName + 'RowById';
165
181
  const getByFiltersFunctionName = 'select' + upperCamelTypeName + 'RowsWithFilters';
@@ -168,235 +184,198 @@ const crudTemplate = (typeName, fields, isView) => {
168
184
  const updateFunctionName = 'update' + upperCamelTypeName + 'Row';
169
185
  const deleteFunctionName = 'delete' + upperCamelTypeName + 'Row';
170
186
  const idType = fields.find((field) => field.name === 'id')?.type || 'string';
171
- const hasIdColumn = idType !== undefined; // Check if 'id' column exists
172
- const exportHeaders = `// Supabase CRUD operations for ${typeName}
173
- // This file is automatically generated. Do not edit it directly.
174
- import { supabase } from "../client";
175
- import { Tables, TablesInsert, TablesUpdate } from "@shared/types";
176
-
177
- type ${typeName} = Tables<'${typeName}'>;
178
- type FilterTypesValue = string | number | boolean | null | Record<string, any>;
179
- type Filters = Record<string, FilterTypesValue | FilterTypesValue[]>;
180
- `;
181
- const exportSelectById = `
182
- // Read single row using 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 {
188
- const result = await supabase
189
- .from('${typeName.toLowerCase()}')
190
- .select('*')
191
- .eq('id', id)
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
-
201
- return result.data as ${typeName};
202
- } catch (error) {
203
- console.error('Error in ${getByIdFunctionName}:', error);
204
- throw error;
205
- }
206
- }
207
- `;
208
- const exportSelectQueries = `
209
- // Function to apply filters to a query
210
- function applyFilters(query: any, filters: Filters): any {
211
- for (const [key, value] of Object.entries(filters)) {
212
- if (Array.isArray(value)) {
213
- query = query.in(key, value); // Use 'in' for array values
214
- } else if (typeof value === 'object' && value !== null) {
215
- for (const [operator, val] of Object.entries(value)) {
216
- switch (operator) {
217
- case 'eq':
218
- query = query.eq(key, val);
219
- break;
220
- case 'neq':
221
- query = query.neq(key, val);
222
- break;
223
- case 'like':
224
- query = query.like(key, val);
225
- break;
226
- case 'ilike':
227
- query = query.ilike(key, val);
228
- break;
229
- case 'lt':
230
- query = query.lt(key, val);
231
- break;
232
- case 'lte':
233
- query = query.lte(key, val);
234
- break;
235
- case 'gte':
236
- query = query.gte(key, val);
237
- break;
238
- case 'gt':
239
- query = query.gt(key, val);
240
- break;
241
- case 'contains':
242
- query = query.contains(key, val);
243
- break;
244
- case 'contains_any':
245
- query = query.contains_any(key, val);
246
- break;
247
- case 'contains_all':
248
- query = query.contains_all(key, val);
249
- break;
250
- // Add more operators as needed
251
- default:
252
- throw new Error('Unsupported operator: ' + operator);
253
- }
254
- }
255
- } else {
256
- query = query.eq(key, value); // Default to 'eq' for simple values
257
- }
258
- }
259
- return query;
260
- }
261
-
262
- // Read multiple rows with dynamic filters
263
- export async function ${getByFiltersFunctionName}({ filters }: { filters: Filters }): Promise<${typeName}[]> {
264
- if (!filters || typeof filters !== 'object') return [];
265
- try {
266
- let query = supabase.from('${typeName.toLowerCase()}').select('*');
267
- query = applyFilters(query, filters);
268
-
269
- const result = await query;
270
-
271
- if (result.error) {
272
- throw new Error(\`Failed to fetch ${typeName}: \${result.error.message}\`);
273
- }
274
-
275
- return (result.data as unknown as ${typeName}[]) || [];
276
- } catch (error) {
277
- console.error('Error in ${getByFiltersFunctionName}:', error);
278
- throw error;
279
- }
280
- }
281
-
282
- // Read a single row with dynamic filters
283
- export async function ${getSingleByFiltersFunctionName}({ filters }: { filters: Filters }): Promise<${typeName} | null> {
284
- if (!filters || typeof filters !== 'object') return null;
285
- try {
286
- let query = supabase.from('${typeName.toLowerCase()}').select('*');
287
- query = applyFilters(query, filters).single();
288
-
289
- const result = await query;
290
-
291
- if (result.error) {
292
- if (result.error.code === 'PGRST116') {
293
- return null;
294
- }
295
- throw new Error(\`Failed to fetch ${typeName}: \${result.error.message}\`);
296
- }
297
-
298
- return result.data as unknown as ${typeName};
299
- } catch (error) {
300
- console.error('Error in ${getSingleByFiltersFunctionName}:', error);
301
- throw error;
302
- }
303
- }
304
- `;
305
- const exportInsertOperation = isView ? '' :
306
- `
307
- // Create Function
308
- export async function ${createFunctionName}({ data }: { data: TablesInsert<'${typeName}'> }): Promise<${typeName}> {
309
- if (!data) {
310
- throw new Error('Data is required for creation');
311
- }
312
- try {
313
- const result = await supabase
314
- .from('${typeName}')
315
- .insert([data])
316
- .select()
317
- .single();
318
-
319
- if (result.error) {
320
- throw new Error(\`Failed to create ${typeName}: \${result.error.message}\`);
321
- }
322
-
323
- if (!result.data) {
324
- throw new Error('No data returned after creation');
325
- }
326
-
327
- return result.data as ${typeName};
328
- } catch (error) {
329
- console.error('Error in ${createFunctionName}:', error);
330
- throw error;
331
- }
332
- }
333
- `;
334
- const exportUpdateOperation = isView ? '' :
335
- `
336
- // Update Function
337
- export async function ${updateFunctionName}({ id, data }: { id: ${idType}; data: TablesUpdate<'${typeName}'> }): Promise<${typeName}> {
338
- if (!id) {
339
- throw new Error('ID is required for update');
340
- }
341
- if (!data || Object.keys(data).length === 0) {
342
- throw new Error('Update data is required');
343
- }
344
- try {
345
- const result = await supabase
346
- .from('${typeName.toLowerCase()}')
347
- .update(data)
348
- .eq('id', id)
349
- .select()
350
- .single();
351
-
352
- if (result.error) {
353
- if (result.error.code === 'PGRST116') {
354
- throw new Error(\`${typeName} with ID \${id} not found\`);
355
- }
356
- throw new Error(\`Failed to update ${typeName}: \${result.error.message}\`);
357
- }
358
-
359
- if (!result.data) {
360
- throw new Error(\`${typeName} with ID \${id} not found\`);
361
- }
362
-
363
- return result.data as ${typeName};
364
- } catch (error) {
365
- console.error('Error in ${updateFunctionName}:', error);
366
- throw error;
367
- }
368
- }
369
- `;
370
- const exportDeleteOperation = isView ? '' :
371
- `
372
- // Delete Function
373
- export async function ${deleteFunctionName}({ id }: { id: ${idType} }): Promise<boolean> {
374
- if (!id) {
375
- throw new Error('ID is required for deletion');
376
- }
377
- try {
378
- const result = await supabase
379
- .from('${typeName.toLowerCase()}')
380
- .delete()
381
- .eq('id', id);
382
-
383
- if (result.error) {
384
- throw new Error(\`Failed to delete ${typeName}: \${result.error.message}\`);
385
- }
386
-
387
- return true;
388
- } catch (error) {
389
- console.error('Error in ${deleteFunctionName}:', error);
390
- throw error;
391
- }
392
- }
393
- `;
394
- // Export all functions
395
- const exportAll = `
396
- // All functions are exported individually above
397
- `;
398
- // Return all the code
399
- return exportHeaders + exportSelectQueries + (hasIdColumn ? exportSelectById : '') + exportInsertOperation + exportUpdateOperation + exportDeleteOperation + exportAll;
187
+ // インポートパスを動的に調整
188
+ const importPath = hasSchemaFolders ? '../../client' : '../client';
189
+ // ヘッダー部分
190
+ const header = [
191
+ `// Supabase CRUD operations for ${typeName} (${schema} schema)`,
192
+ '// 自動生成ファイル',
193
+ `import { supabase } from "${importPath}";`,
194
+ 'import { Tables, TablesInsert, TablesUpdate } from "@shared/types";',
195
+ '',
196
+ `type ${typeName} = Tables<'${typeName}'>;`,
197
+ 'type FilterTypesValue = string | number | boolean | null | Record<string, any>;',
198
+ 'type Filters = Record<string, FilterTypesValue | FilterTypesValue[]>;',
199
+ ''
200
+ ].join('\n');
201
+ // フィルター適用関数
202
+ const filterFunction = [
203
+ '/**',
204
+ ' * フィルター適用関数',
205
+ ' */',
206
+ 'function applyFilters(query: any, filters: Filters): any {',
207
+ ' for (const [key, value] of Object.entries(filters)) {',
208
+ ' if (Array.isArray(value)) {',
209
+ ' query = query.in(key, value);',
210
+ ' } else if (typeof value === "object" && value !== null) {',
211
+ ' for (const [operator, val] of Object.entries(value)) {',
212
+ ' switch (operator) {',
213
+ ' case "eq": query = query.eq(key, val); break;',
214
+ ' case "neq": query = query.neq(key, val); break;',
215
+ ' case "like": query = query.like(key, val); break;',
216
+ ' case "ilike": query = query.ilike(key, val); break;',
217
+ ' case "lt": query = query.lt(key, val); break;',
218
+ ' case "lte": query = query.lte(key, val); break;',
219
+ ' case "gte": query = query.gte(key, val); break;',
220
+ ' case "gt": query = query.gt(key, val); break;',
221
+ ' case "contains": query = query.contains(key, val); break;',
222
+ ' case "contains_any": query = query.contains_any(key, val); break;',
223
+ ' case "contains_all": query = query.contains_all(key, val); break;',
224
+ ' default: throw new Error("Unsupported operator: " + operator);',
225
+ ' }',
226
+ ' }',
227
+ ' } else {',
228
+ ' query = query.eq(key, value);',
229
+ ' }',
230
+ ' }',
231
+ ' return query;',
232
+ '}',
233
+ ''
234
+ ].join('\n');
235
+ // IDで1件取得
236
+ const selectById = [
237
+ '/**',
238
+ ' * IDで1件取得',
239
+ ' */',
240
+ `export async function ${getByIdFunctionName}({ id }: { id: ${idType} }): Promise<${typeName} | null> {`,
241
+ ' if (!id) throw new Error("ID is required");',
242
+ ' try {',
243
+ ' const result = await supabase',
244
+ ` .schema("${schema}")`,
245
+ ` .from("${typeName.toLowerCase()}")`,
246
+ ' .select("*")',
247
+ ' .eq("id", id)',
248
+ ' .single();',
249
+ ' if (result.error) {',
250
+ ' if (result.error.code === "PGRST116") return null;',
251
+ ` throw new Error(\`Failed to fetch ${typeName}: \${result.error.message}\`);`,
252
+ ' }',
253
+ ` return result.data as ${typeName};`,
254
+ ' } catch (error) {',
255
+ ` console.error("Error in ${getByIdFunctionName}:", error);`,
256
+ ' throw error;',
257
+ ' }',
258
+ '}',
259
+ ''
260
+ ].join('\n');
261
+ // フィルターで複数取得
262
+ const selectMultiple = [
263
+ '/**',
264
+ ' * フィルターで複数取得',
265
+ ' */',
266
+ `export async function ${getByFiltersFunctionName}({ filters }: { filters: Filters }): Promise<${typeName}[]> {`,
267
+ ' if (!filters || typeof filters !== "object") return [];',
268
+ ' try {',
269
+ ` let query = supabase.schema("${schema}").from("${typeName.toLowerCase()}").select("*");`,
270
+ ' query = applyFilters(query, filters);',
271
+ ' const result = await query;',
272
+ ` if (result.error) throw new Error(\`Failed to fetch ${typeName}: \${result.error.message}\`);`,
273
+ ` return (result.data as unknown as ${typeName}[]) || [];`,
274
+ ' } catch (error) {',
275
+ ` console.error("Error in ${getByFiltersFunctionName}:", error);`,
276
+ ' throw error;',
277
+ ' }',
278
+ '}',
279
+ ''
280
+ ].join('\n');
281
+ // フィルターで1件取得
282
+ const selectSingle = [
283
+ '/**',
284
+ ' * フィルターで1件取得',
285
+ ' */',
286
+ `export async function ${getSingleByFiltersFunctionName}({ filters }: { filters: Filters }): Promise<${typeName} | null> {`,
287
+ ' if (!filters || typeof filters !== "object") return null;',
288
+ ' try {',
289
+ ` let query = supabase.schema("${schema}").from("${typeName.toLowerCase()}").select("*");`,
290
+ ' query = applyFilters(query, filters).single();',
291
+ ' const result = await query;',
292
+ ' if (result.error) {',
293
+ ' if (result.error.code === "PGRST116") return null;',
294
+ ` throw new Error(\`Failed to fetch ${typeName}: \${result.error.message}\`);`,
295
+ ' }',
296
+ ` return result.data as unknown as ${typeName};`,
297
+ ' } catch (error) {',
298
+ ` console.error("Error in ${getSingleByFiltersFunctionName}:", error);`,
299
+ ' throw error;',
300
+ ' }',
301
+ '}',
302
+ ''
303
+ ].join('\n');
304
+ // 追加(ビューでない場合のみ)
305
+ const insertOperation = isView ? '' : [
306
+ '/**',
307
+ ' * 追加',
308
+ ' */',
309
+ `export async function ${createFunctionName}({ data }: { data: TablesInsert<"${typeName}"> }): Promise<${typeName}> {`,
310
+ ' if (!data) throw new Error("Data is required for creation");',
311
+ ' try {',
312
+ ' const result = await supabase',
313
+ ` .schema("${schema}")`,
314
+ ` .from("${typeName.toLowerCase()}")`,
315
+ ' .insert([data])',
316
+ ' .select()',
317
+ ' .single();',
318
+ ` if (result.error) throw new Error(\`Failed to create ${typeName}: \${result.error.message}\`);`,
319
+ ' if (!result.data) throw new Error("No data returned after creation");',
320
+ ` return result.data as ${typeName};`,
321
+ ' } catch (error) {',
322
+ ` console.error("Error in ${createFunctionName}:", error);`,
323
+ ' throw error;',
324
+ ' }',
325
+ '}',
326
+ ''
327
+ ].join('\n');
328
+ // 更新(ビューでない場合のみ)
329
+ const updateOperation = isView ? '' : [
330
+ '/**',
331
+ ' * 更新',
332
+ ' */',
333
+ `export async function ${updateFunctionName}({ id, data }: { id: ${idType}; data: TablesUpdate<"${typeName}"> }): Promise<${typeName}> {`,
334
+ ' if (!id) throw new Error("ID is required for update");',
335
+ ' if (!data || Object.keys(data).length === 0) throw new Error("Update data is required");',
336
+ ' try {',
337
+ ' const result = await supabase',
338
+ ` .schema("${schema}")`,
339
+ ` .from("${typeName.toLowerCase()}")`,
340
+ ' .update(data)',
341
+ ' .eq("id", id)',
342
+ ' .select()',
343
+ ' .single();',
344
+ ' if (result.error) {',
345
+ ` if (result.error.code === "PGRST116") throw new Error(\`${typeName} with ID \${id} not found\`);`,
346
+ ` throw new Error(\`Failed to update ${typeName}: \${result.error.message}\`);`,
347
+ ' }',
348
+ ` if (!result.data) throw new Error(\`${typeName} with ID \${id} not found\`);`,
349
+ ` return result.data as ${typeName};`,
350
+ ' } catch (error) {',
351
+ ` console.error("Error in ${updateFunctionName}:", error);`,
352
+ ' throw error;',
353
+ ' }',
354
+ '}',
355
+ ''
356
+ ].join('\n');
357
+ // 削除(ビューでない場合のみ)
358
+ const deleteOperation = isView ? '' : [
359
+ '/**',
360
+ ' * 削除',
361
+ ' */',
362
+ `export async function ${deleteFunctionName}({ id }: { id: ${idType} }): Promise<boolean> {`,
363
+ ' if (!id) throw new Error("ID is required for deletion");',
364
+ ' try {',
365
+ ' const result = await supabase',
366
+ ` .schema("${schema}")`,
367
+ ` .from("${typeName.toLowerCase()}")`,
368
+ ' .delete()',
369
+ ' .eq("id", id);',
370
+ ` if (result.error) throw new Error(\`Failed to delete ${typeName}: \${result.error.message}\`);`,
371
+ ' return true;',
372
+ ' } catch (error) {',
373
+ ` console.error("Error in ${deleteFunctionName}:", error);`,
374
+ ' throw error;',
375
+ ' }',
376
+ '}',
377
+ ''
378
+ ].join('\n');
379
+ // 全体を結合
380
+ return header + filterFunction + selectById + selectMultiple + selectSingle + insertOperation + updateOperation + deleteOperation;
400
381
  };
401
- // console.log(crudFolderPath);
402
- // console.log(types);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supatool",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "A CLI tool for Supabase schema extraction and TypeScript CRUD generation with declarative database schema support.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",