swallowkit 1.0.0-beta.5 → 1.0.0-beta.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.
@@ -16,224 +16,224 @@ function generateCompactAzureFunctionsCRUD(model, sharedPackageName) {
16
16
  const modelCamel = (0, model_parser_1.toCamelCase)(modelName);
17
17
  const modelKebab = (0, model_parser_1.toKebabCase)(modelName);
18
18
  const schemaName = model.schemaName;
19
- return `import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
20
- import { z } from 'zod/v4';
21
- import crypto from 'crypto';
22
- import { ${schemaName} } from '${sharedPackageName}';
23
-
24
- const containerName = '${modelName}s';
25
-
26
- // GET /api/${modelCamel} - 全件取得
27
- app.http('${modelCamel}-get-all', {
28
- methods: ['GET'],
29
- route: '${modelCamel}',
30
- authLevel: 'anonymous',
31
- extraInputs: [
32
- {
33
- type: 'cosmosDB',
34
- name: 'cosmosInput',
35
- databaseName: process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase',
36
- containerName,
37
- connection: 'CosmosDBConnection',
38
- sqlQuery: 'SELECT * FROM c',
39
- },
40
- ],
41
- handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
42
- try {
43
- const documents = context.extraInputs.get('cosmosInput') as any[];
44
-
45
- if (!documents || !Array.isArray(documents)) {
46
- return { status: 200, jsonBody: [] };
47
- }
48
-
49
- const validated = z.array(${schemaName}).parse(documents);
50
- context.log(\`Fetched \${validated.length} items from \${containerName}\`);
51
-
52
- return { status: 200, jsonBody: validated };
53
- } catch (error) {
54
- context.error(\`Error fetching from \${containerName}:\`, error);
55
- return { status: 500, jsonBody: { error: 'Failed to fetch items' } };
56
- }
57
- },
58
- });
59
-
60
- // GET /api/${modelCamel}/{id} - ID指定取得
61
- app.http('${modelCamel}-get-by-id', {
62
- methods: ['GET'],
63
- route: '${modelCamel}/{id}',
64
- authLevel: 'anonymous',
65
- extraInputs: [
66
- {
67
- type: 'cosmosDB',
68
- name: 'cosmosInput',
69
- databaseName: process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase',
70
- containerName,
71
- connection: 'CosmosDBConnection',
72
- id: '{id}',
73
- partitionKey: '{id}',
74
- },
75
- ],
76
- handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
77
- try {
78
- const document = context.extraInputs.get('cosmosInput');
79
-
80
- if (!document) {
81
- return { status: 404, jsonBody: { error: 'Item not found' } };
82
- }
83
-
84
- const validated = ${schemaName}.parse(document);
85
- return { status: 200, jsonBody: validated };
86
- } catch (error) {
87
- context.error(\`Error fetching item from \${containerName}:\`, error);
88
- return { status: 500, jsonBody: { error: 'Failed to fetch item' } };
89
- }
90
- },
91
- });
92
-
93
- // POST /api/${modelCamel} - 新規作成
94
- app.http('${modelCamel}-create', {
95
- methods: ['POST'],
96
- route: '${modelCamel}',
97
- authLevel: 'anonymous',
98
- extraOutputs: [
99
- {
100
- type: 'cosmosDB',
101
- name: 'cosmosOutput',
102
- databaseName: process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase',
103
- containerName,
104
- connection: 'CosmosDBConnection',
105
- createIfNotExists: true,
106
- },
107
- ],
108
- handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
109
- try {
110
- const body = await request.json() as any;
111
-
112
- const { id, createdAt, updatedAt, ...userData } = body;
113
- const now = new Date().toISOString();
114
- const dataWithManagedFields = {
115
- ...userData,
116
- id: id || crypto.randomUUID(),
117
- createdAt: now,
118
- updatedAt: now,
119
- };
120
-
121
- const result = ${schemaName}.safeParse(dataWithManagedFields);
122
-
123
- if (!result.success) {
124
- context.error('Validation failed:', result.error.issues);
125
- return { status: 400, jsonBody: { error: 'Validation failed', details: result.error.issues } };
126
- }
127
-
128
- context.extraOutputs.set('cosmosOutput', result.data);
129
- return { status: 201, jsonBody: result.data };
130
- } catch (error) {
131
- context.error(\`Error creating item in \${containerName}:\`, error);
132
- return { status: 500, jsonBody: { error: 'Failed to create item' } };
133
- }
134
- },
135
- });
136
-
137
- // PUT /api/${modelCamel}/{id} - 更新
138
- app.http('${modelCamel}-update', {
139
- methods: ['PUT'],
140
- route: '${modelCamel}/{id}',
141
- authLevel: 'anonymous',
142
- extraInputs: [
143
- {
144
- type: 'cosmosDB',
145
- name: 'cosmosInput',
146
- databaseName: process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase',
147
- containerName,
148
- connection: 'CosmosDBConnection',
149
- id: '{id}',
150
- partitionKey: '{id}',
151
- },
152
- ],
153
- extraOutputs: [
154
- {
155
- type: 'cosmosDB',
156
- name: 'cosmosOutput',
157
- databaseName: process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase',
158
- containerName,
159
- connection: 'CosmosDBConnection',
160
- },
161
- ],
162
- handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
163
- try {
164
- const id = request.params.id;
165
- if (!id) {
166
- return { status: 400, jsonBody: { error: 'ID is required' } };
167
- }
168
-
169
- const existingDocument = context.extraInputs.get('cosmosInput') as any;
170
- if (!existingDocument) {
171
- return { status: 404, jsonBody: { error: 'Item not found' } };
172
- }
173
-
174
- const body = await request.json() as any;
175
- const { createdAt, updatedAt, ...userData } = body;
176
-
177
- const dataWithManagedFields = {
178
- ...userData,
179
- id,
180
- createdAt: existingDocument.createdAt,
181
- updatedAt: new Date().toISOString(),
182
- };
183
-
184
- const result = ${schemaName}.safeParse(dataWithManagedFields);
185
-
186
- if (!result.success) {
187
- context.error('Validation failed:', result.error.issues);
188
- return { status: 400, jsonBody: { error: 'Validation failed', details: result.error.issues } };
189
- }
190
-
191
- context.extraOutputs.set('cosmosOutput', result.data);
192
- return { status: 200, jsonBody: result.data };
193
- } catch (error) {
194
- context.error(\`Error updating item in \${containerName}:\`, error);
195
- return { status: 500, jsonBody: { error: 'Failed to update item' } };
196
- }
197
- },
198
- });
199
-
200
- // DELETE /api/${modelCamel}/{id} - 削除
201
- app.http('${modelCamel}-delete', {
202
- methods: ['DELETE'],
203
- route: '${modelCamel}/{id}',
204
- authLevel: 'anonymous',
205
- handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
206
- try {
207
- const id = request.params.id;
208
- if (!id) {
209
- return { status: 400, jsonBody: { error: 'ID is required' } };
210
- }
211
-
212
- const { CosmosClient } = await import('@azure/cosmos');
213
- const endpoint = process.env.CosmosDBConnection__accountEndpoint;
214
- let client: InstanceType<typeof CosmosClient>;
215
- if (endpoint) {
216
- const { DefaultAzureCredential } = await import('@azure/identity');
217
- client = new CosmosClient({ endpoint, aadCredentials: new DefaultAzureCredential() });
218
- } else {
219
- client = new CosmosClient(process.env.CosmosDBConnection!);
220
- }
221
- const database = client.database(process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase');
222
- const container = database.container(containerName);
223
-
224
- await container.item(id, id).delete();
225
- context.log(\`Deleted item \${id} from \${containerName}\`);
226
-
227
- return { status: 204 };
228
- } catch (error: any) {
229
- if (error.code === 404) {
230
- return { status: 404, jsonBody: { error: 'Item not found' } };
231
- }
232
- context.error(\`Error deleting item from \${containerName}:\`, error);
233
- return { status: 500, jsonBody: { error: 'Failed to delete item' } };
234
- }
235
- },
236
- });
19
+ return `import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
20
+ import { z } from 'zod/v4';
21
+ import crypto from 'crypto';
22
+ import { ${schemaName} } from '${sharedPackageName}';
23
+
24
+ const containerName = '${modelName}s';
25
+
26
+ // GET /api/${modelCamel} - 全件取得
27
+ app.http('${modelCamel}-get-all', {
28
+ methods: ['GET'],
29
+ route: '${modelCamel}',
30
+ authLevel: 'anonymous',
31
+ extraInputs: [
32
+ {
33
+ type: 'cosmosDB',
34
+ name: 'cosmosInput',
35
+ databaseName: process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase',
36
+ containerName,
37
+ connection: 'CosmosDBConnection',
38
+ sqlQuery: 'SELECT * FROM c',
39
+ },
40
+ ],
41
+ handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
42
+ try {
43
+ const documents = context.extraInputs.get('cosmosInput') as any[];
44
+
45
+ if (!documents || !Array.isArray(documents)) {
46
+ return { status: 200, jsonBody: [] };
47
+ }
48
+
49
+ const validated = z.array(${schemaName}).parse(documents);
50
+ context.log(\`Fetched \${validated.length} items from \${containerName}\`);
51
+
52
+ return { status: 200, jsonBody: validated };
53
+ } catch (error) {
54
+ context.error(\`Error fetching from \${containerName}:\`, error);
55
+ return { status: 500, jsonBody: { error: 'Failed to fetch items' } };
56
+ }
57
+ },
58
+ });
59
+
60
+ // GET /api/${modelCamel}/{id} - ID指定取得
61
+ app.http('${modelCamel}-get-by-id', {
62
+ methods: ['GET'],
63
+ route: '${modelCamel}/{id}',
64
+ authLevel: 'anonymous',
65
+ extraInputs: [
66
+ {
67
+ type: 'cosmosDB',
68
+ name: 'cosmosInput',
69
+ databaseName: process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase',
70
+ containerName,
71
+ connection: 'CosmosDBConnection',
72
+ id: '{id}',
73
+ partitionKey: '{id}',
74
+ },
75
+ ],
76
+ handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
77
+ try {
78
+ const document = context.extraInputs.get('cosmosInput');
79
+
80
+ if (!document) {
81
+ return { status: 404, jsonBody: { error: 'Item not found' } };
82
+ }
83
+
84
+ const validated = ${schemaName}.parse(document);
85
+ return { status: 200, jsonBody: validated };
86
+ } catch (error) {
87
+ context.error(\`Error fetching item from \${containerName}:\`, error);
88
+ return { status: 500, jsonBody: { error: 'Failed to fetch item' } };
89
+ }
90
+ },
91
+ });
92
+
93
+ // POST /api/${modelCamel} - 新規作成
94
+ app.http('${modelCamel}-create', {
95
+ methods: ['POST'],
96
+ route: '${modelCamel}',
97
+ authLevel: 'anonymous',
98
+ extraOutputs: [
99
+ {
100
+ type: 'cosmosDB',
101
+ name: 'cosmosOutput',
102
+ databaseName: process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase',
103
+ containerName,
104
+ connection: 'CosmosDBConnection',
105
+ createIfNotExists: true,
106
+ },
107
+ ],
108
+ handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
109
+ try {
110
+ const body = await request.json() as any;
111
+
112
+ const { id, createdAt, updatedAt, ...userData } = body;
113
+ const now = new Date().toISOString();
114
+ const dataWithManagedFields = {
115
+ ...userData,
116
+ id: id || crypto.randomUUID(),
117
+ createdAt: now,
118
+ updatedAt: now,
119
+ };
120
+
121
+ const result = ${schemaName}.safeParse(dataWithManagedFields);
122
+
123
+ if (!result.success) {
124
+ context.error('Validation failed:', result.error.issues);
125
+ return { status: 400, jsonBody: { error: 'Validation failed', details: result.error.issues } };
126
+ }
127
+
128
+ context.extraOutputs.set('cosmosOutput', result.data);
129
+ return { status: 201, jsonBody: result.data };
130
+ } catch (error) {
131
+ context.error(\`Error creating item in \${containerName}:\`, error);
132
+ return { status: 500, jsonBody: { error: 'Failed to create item' } };
133
+ }
134
+ },
135
+ });
136
+
137
+ // PUT /api/${modelCamel}/{id} - 更新
138
+ app.http('${modelCamel}-update', {
139
+ methods: ['PUT'],
140
+ route: '${modelCamel}/{id}',
141
+ authLevel: 'anonymous',
142
+ extraInputs: [
143
+ {
144
+ type: 'cosmosDB',
145
+ name: 'cosmosInput',
146
+ databaseName: process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase',
147
+ containerName,
148
+ connection: 'CosmosDBConnection',
149
+ id: '{id}',
150
+ partitionKey: '{id}',
151
+ },
152
+ ],
153
+ extraOutputs: [
154
+ {
155
+ type: 'cosmosDB',
156
+ name: 'cosmosOutput',
157
+ databaseName: process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase',
158
+ containerName,
159
+ connection: 'CosmosDBConnection',
160
+ },
161
+ ],
162
+ handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
163
+ try {
164
+ const id = request.params.id;
165
+ if (!id) {
166
+ return { status: 400, jsonBody: { error: 'ID is required' } };
167
+ }
168
+
169
+ const existingDocument = context.extraInputs.get('cosmosInput') as any;
170
+ if (!existingDocument) {
171
+ return { status: 404, jsonBody: { error: 'Item not found' } };
172
+ }
173
+
174
+ const body = await request.json() as any;
175
+ const { createdAt, updatedAt, ...userData } = body;
176
+
177
+ const dataWithManagedFields = {
178
+ ...userData,
179
+ id,
180
+ createdAt: existingDocument.createdAt,
181
+ updatedAt: new Date().toISOString(),
182
+ };
183
+
184
+ const result = ${schemaName}.safeParse(dataWithManagedFields);
185
+
186
+ if (!result.success) {
187
+ context.error('Validation failed:', result.error.issues);
188
+ return { status: 400, jsonBody: { error: 'Validation failed', details: result.error.issues } };
189
+ }
190
+
191
+ context.extraOutputs.set('cosmosOutput', result.data);
192
+ return { status: 200, jsonBody: result.data };
193
+ } catch (error) {
194
+ context.error(\`Error updating item in \${containerName}:\`, error);
195
+ return { status: 500, jsonBody: { error: 'Failed to update item' } };
196
+ }
197
+ },
198
+ });
199
+
200
+ // DELETE /api/${modelCamel}/{id} - 削除
201
+ app.http('${modelCamel}-delete', {
202
+ methods: ['DELETE'],
203
+ route: '${modelCamel}/{id}',
204
+ authLevel: 'anonymous',
205
+ handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
206
+ try {
207
+ const id = request.params.id;
208
+ if (!id) {
209
+ return { status: 400, jsonBody: { error: 'ID is required' } };
210
+ }
211
+
212
+ const { CosmosClient } = await import('@azure/cosmos');
213
+ const endpoint = process.env.CosmosDBConnection__accountEndpoint;
214
+ let client: InstanceType<typeof CosmosClient>;
215
+ if (endpoint) {
216
+ const { DefaultAzureCredential } = await import('@azure/identity');
217
+ client = new CosmosClient({ endpoint, aadCredentials: new DefaultAzureCredential() });
218
+ } else {
219
+ client = new CosmosClient(process.env.CosmosDBConnection!);
220
+ }
221
+ const database = client.database(process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase');
222
+ const container = database.container(containerName);
223
+
224
+ await container.item(id, id).delete();
225
+ context.log(\`Deleted item \${id} from \${containerName}\`);
226
+
227
+ return { status: 204 };
228
+ } catch (error: any) {
229
+ if (error.code === 404) {
230
+ return { status: 404, jsonBody: { error: 'Item not found' } };
231
+ }
232
+ context.error(\`Error deleting item from \${containerName}:\`, error);
233
+ return { status: 500, jsonBody: { error: 'Failed to delete item' } };
234
+ }
235
+ },
236
+ });
237
237
  `;
238
238
  }
239
239
  //# sourceMappingURL=functions-generator.js.map
@@ -328,104 +328,104 @@ async function extractFieldsFromSchema(modelPath, schemaName) {
328
328
  }
329
329
  // プロジェクトルート内に一時スクリプトファイルを作成
330
330
  const tempScript = path.join(projectRoot, `.swallowkit-parser-${Date.now()}.mjs`);
331
- const scriptCode = `
332
- import { z } from 'zod/v4';
333
-
334
- // インライン化した依存スキーマ
335
- ${inlinedDeps}
336
- // モデルファイルの内容を評価
337
- ${modelContent}
338
-
339
- const schema = ${schemaName};
340
-
341
- // Zod v3とv4の両方に対応
342
- const isObject = (schema && schema._def &&
343
- (schema._def.typeName === 'ZodObject' || schema.constructor?.name === 'ZodObject' || typeof schema._def.shape === 'function'));
344
-
345
- if (isObject) {
346
- const shape = typeof schema._def.shape === 'function' ? schema._def.shape() : schema._def.shape;
347
- const fields = Object.keys(shape).map(key => {
348
- const field = shape[key];
349
- let type = 'string';
350
- let isOptional = false;
351
- let isArray = false;
352
- let enumValues = undefined;
353
-
354
- // ZodOptional, ZodDefault, ZodEffects を unwrap
355
- let fieldDef = field;
356
- const getTypeName = (def) => def?._def?.typeName || def?.constructor?.name || '';
357
-
358
- // 繰り返し unwrap(複数のラッパーがある場合に対応)
359
- let unwrapped = false;
360
- do {
361
- unwrapped = false;
362
- const typeName = getTypeName(fieldDef);
363
-
364
- if (typeName === 'ZodOptional') {
365
- isOptional = true;
366
- fieldDef = fieldDef._def.innerType;
367
- unwrapped = true;
368
- } else if (typeName === 'ZodDefault') {
369
- // .default() は optional と同様に扱う
370
- isOptional = true;
371
- fieldDef = fieldDef._def.innerType;
372
- unwrapped = true;
373
- } else if (typeName === 'ZodEffects') {
374
- // .min(), .max(), .regex() などの Effects を unwrap
375
- fieldDef = fieldDef._def.schema;
376
- unwrapped = true;
377
- }
378
- } while (unwrapped);
379
-
380
- // ZodArray をチェック
381
- if (getTypeName(fieldDef) === 'ZodArray') {
382
- isArray = true;
383
- fieldDef = fieldDef._def.type || fieldDef._def.element;
384
- }
385
-
386
- // 基本型を判定
387
- const typeName = getTypeName(fieldDef);
388
- if (typeName === 'ZodString') type = 'string';
389
- else if (typeName === 'ZodNumber') type = 'number';
390
- else if (typeName === 'ZodBoolean') type = 'boolean';
391
- else if (typeName === 'ZodDate') type = 'date';
392
- else if (typeName === 'ZodObject') type = 'object';
393
- else if (typeName === 'ZodEnum' || typeName === 'ZodNativeEnum') {
394
- type = 'string';
395
- // enum の選択肢を取得(複数のZodバージョンに対応)
396
- if (fieldDef.options) {
397
- // Zod v3.23+ では options プロパティを使用
398
- enumValues = Array.isArray(fieldDef.options)
399
- ? fieldDef.options
400
- : Object.values(fieldDef.options);
401
- } else if (fieldDef._def.values) {
402
- // 古いバージョンでは _def.values を使用
403
- enumValues = Array.isArray(fieldDef._def.values)
404
- ? fieldDef._def.values
405
- : Object.values(fieldDef._def.values);
406
- } else if (fieldDef._def.entries) {
407
- // さらに古いバージョンでは _def.entries を使用
408
- enumValues = Array.isArray(fieldDef._def.entries)
409
- ? fieldDef._def.entries
410
- : Object.values(fieldDef._def.entries);
411
- }
412
- }
413
-
414
- // 外部キー検出: フィールド名が <ModelName>Id のパターンの場合
415
- let isForeignKey = false;
416
- let referencedModel = undefined;
417
- if (key.endsWith('Id') && key.length > 2 && type === 'string') {
418
- // categoryId -> Category, userId -> User など
419
- const modelName = key.slice(0, -2); // "Id" を除去
420
- referencedModel = modelName.charAt(0).toUpperCase() + modelName.slice(1); // 先頭を大文字に
421
- isForeignKey = true;
422
- }
423
-
424
- return { name: key, type, isOptional, isArray, enumValues, isForeignKey, referencedModel };
425
- });
426
-
427
- console.log(JSON.stringify(fields));
428
- }
331
+ const scriptCode = `
332
+ import { z } from 'zod/v4';
333
+
334
+ // インライン化した依存スキーマ
335
+ ${inlinedDeps}
336
+ // モデルファイルの内容を評価
337
+ ${modelContent}
338
+
339
+ const schema = ${schemaName};
340
+
341
+ // Zod v3とv4の両方に対応
342
+ const isObject = (schema && schema._def &&
343
+ (schema._def.typeName === 'ZodObject' || schema.constructor?.name === 'ZodObject' || typeof schema._def.shape === 'function'));
344
+
345
+ if (isObject) {
346
+ const shape = typeof schema._def.shape === 'function' ? schema._def.shape() : schema._def.shape;
347
+ const fields = Object.keys(shape).map(key => {
348
+ const field = shape[key];
349
+ let type = 'string';
350
+ let isOptional = false;
351
+ let isArray = false;
352
+ let enumValues = undefined;
353
+
354
+ // ZodOptional, ZodDefault, ZodEffects を unwrap
355
+ let fieldDef = field;
356
+ const getTypeName = (def) => def?._def?.typeName || def?.constructor?.name || '';
357
+
358
+ // 繰り返し unwrap(複数のラッパーがある場合に対応)
359
+ let unwrapped = false;
360
+ do {
361
+ unwrapped = false;
362
+ const typeName = getTypeName(fieldDef);
363
+
364
+ if (typeName === 'ZodOptional') {
365
+ isOptional = true;
366
+ fieldDef = fieldDef._def.innerType;
367
+ unwrapped = true;
368
+ } else if (typeName === 'ZodDefault') {
369
+ // .default() は optional と同様に扱う
370
+ isOptional = true;
371
+ fieldDef = fieldDef._def.innerType;
372
+ unwrapped = true;
373
+ } else if (typeName === 'ZodEffects') {
374
+ // .min(), .max(), .regex() などの Effects を unwrap
375
+ fieldDef = fieldDef._def.schema;
376
+ unwrapped = true;
377
+ }
378
+ } while (unwrapped);
379
+
380
+ // ZodArray をチェック
381
+ if (getTypeName(fieldDef) === 'ZodArray') {
382
+ isArray = true;
383
+ fieldDef = fieldDef._def.type || fieldDef._def.element;
384
+ }
385
+
386
+ // 基本型を判定
387
+ const typeName = getTypeName(fieldDef);
388
+ if (typeName === 'ZodString') type = 'string';
389
+ else if (typeName === 'ZodNumber') type = 'number';
390
+ else if (typeName === 'ZodBoolean') type = 'boolean';
391
+ else if (typeName === 'ZodDate') type = 'date';
392
+ else if (typeName === 'ZodObject') type = 'object';
393
+ else if (typeName === 'ZodEnum' || typeName === 'ZodNativeEnum') {
394
+ type = 'string';
395
+ // enum の選択肢を取得(複数のZodバージョンに対応)
396
+ if (fieldDef.options) {
397
+ // Zod v3.23+ では options プロパティを使用
398
+ enumValues = Array.isArray(fieldDef.options)
399
+ ? fieldDef.options
400
+ : Object.values(fieldDef.options);
401
+ } else if (fieldDef._def.values) {
402
+ // 古いバージョンでは _def.values を使用
403
+ enumValues = Array.isArray(fieldDef._def.values)
404
+ ? fieldDef._def.values
405
+ : Object.values(fieldDef._def.values);
406
+ } else if (fieldDef._def.entries) {
407
+ // さらに古いバージョンでは _def.entries を使用
408
+ enumValues = Array.isArray(fieldDef._def.entries)
409
+ ? fieldDef._def.entries
410
+ : Object.values(fieldDef._def.entries);
411
+ }
412
+ }
413
+
414
+ // 外部キー検出: フィールド名が <ModelName>Id のパターンの場合
415
+ let isForeignKey = false;
416
+ let referencedModel = undefined;
417
+ if (key.endsWith('Id') && key.length > 2 && type === 'string') {
418
+ // categoryId -> Category, userId -> User など
419
+ const modelName = key.slice(0, -2); // "Id" を除去
420
+ referencedModel = modelName.charAt(0).toUpperCase() + modelName.slice(1); // 先頭を大文字に
421
+ isForeignKey = true;
422
+ }
423
+
424
+ return { name: key, type, isOptional, isArray, enumValues, isForeignKey, referencedModel };
425
+ });
426
+
427
+ console.log(JSON.stringify(fields));
428
+ }
429
429
  `;
430
430
  writeFileSync(tempScript, scriptCode, 'utf8');
431
431
  try {