swallowkit 1.0.0-beta.16 → 1.0.0-beta.18
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/dist/__tests__/fixtures.d.ts.map +1 -1
- package/dist/__tests__/fixtures.js +1 -0
- package/dist/__tests__/fixtures.js.map +1 -1
- package/dist/cli/commands/dev-seeds.js +4 -4
- package/dist/cli/commands/dev-seeds.js.map +1 -1
- package/dist/cli/commands/dev.js +14 -3
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.js +4 -4
- package/dist/cli/commands/scaffold.js +1 -1
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/core/scaffold/functions-generator.d.ts.map +1 -1
- package/dist/core/scaffold/functions-generator.js +341 -85
- package/dist/core/scaffold/functions-generator.js.map +1 -1
- package/dist/core/scaffold/model-parser.d.ts +7 -0
- package/dist/core/scaffold/model-parser.d.ts.map +1 -1
- package/dist/core/scaffold/model-parser.js +23 -0
- package/dist/core/scaffold/model-parser.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/functions-generator.test.ts.snap +694 -0
- package/src/__tests__/fixtures.ts +1 -0
- package/src/__tests__/functions-generator.test.ts +136 -0
- package/src/__tests__/model-parser.test.ts +72 -0
- package/src/cli/commands/dev-seeds.ts +4 -4
- package/src/cli/commands/dev.ts +15 -3
- package/src/cli/commands/init.ts +4 -4
- package/src/cli/commands/scaffold.ts +1 -1
- package/src/core/scaffold/functions-generator.ts +365 -85
- package/src/core/scaffold/model-parser.ts +26 -0
|
@@ -19,6 +19,9 @@ function generateCompactAzureFunctionsCRUD(model, sharedPackageName, authPolicy)
|
|
|
19
19
|
const modelCamel = (0, model_parser_1.toCamelCase)(modelName);
|
|
20
20
|
const modelKebab = (0, model_parser_1.toKebabCase)(modelName);
|
|
21
21
|
const schemaName = model.schemaName;
|
|
22
|
+
const partitionKeyPath = model.partitionKey; // e.g. "/tenantId"
|
|
23
|
+
const partitionKeyField = partitionKeyPath.slice(1); // e.g. "tenantId"
|
|
24
|
+
const isIdPartition = partitionKeyField === 'id';
|
|
22
25
|
const hasAuth = !!authPolicy;
|
|
23
26
|
const authImport = hasAuth ? `\n${(0, auth_generator_1.generateAuthImportTS)()}\n` : '';
|
|
24
27
|
const readGuard = hasAuth ? `\n${(0, auth_generator_1.generateAuthGuardTS)(authPolicy, 'read')}\n` : '';
|
|
@@ -27,6 +30,30 @@ function generateCompactAzureFunctionsCRUD(model, sharedPackageName, authPolicy)
|
|
|
27
30
|
? ` const authErr = handleAuthError(error);
|
|
28
31
|
if (authErr) return authErr;\n`
|
|
29
32
|
: '';
|
|
33
|
+
// SDK クライアント初期化ヘルパー(PK≠/id の場合、および delete で使用)
|
|
34
|
+
const sdkClientInit = `const { CosmosClient } = await import('@azure/cosmos');
|
|
35
|
+
const endpoint = process.env.CosmosDBConnection__accountEndpoint;
|
|
36
|
+
let client: InstanceType<typeof CosmosClient>;
|
|
37
|
+
if (endpoint) {
|
|
38
|
+
const { DefaultAzureCredential } = await import('@azure/identity');
|
|
39
|
+
client = new CosmosClient({ endpoint, aadCredentials: new DefaultAzureCredential() });
|
|
40
|
+
} else {
|
|
41
|
+
client = new CosmosClient(process.env.CosmosDBConnection!);
|
|
42
|
+
}
|
|
43
|
+
const database = client.database(process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase');
|
|
44
|
+
const container = database.container(containerName);`;
|
|
45
|
+
// getById ハンドラー: PK=/id の場合は input binding、それ以外は SDK
|
|
46
|
+
const getByIdHandler = isIdPartition
|
|
47
|
+
? generateGetByIdWithBinding(modelCamel, schemaName, readGuard, authCatchBlock)
|
|
48
|
+
: generateGetByIdWithSdk(modelCamel, schemaName, readGuard, authCatchBlock, sdkClientInit);
|
|
49
|
+
// update ハンドラー: PK=/id の場合は input binding、それ以外は SDK
|
|
50
|
+
const updateHandler = isIdPartition
|
|
51
|
+
? generateUpdateWithBinding(modelCamel, schemaName, writeGuard, authCatchBlock)
|
|
52
|
+
: generateUpdateWithSdk(modelCamel, schemaName, partitionKeyField, writeGuard, authCatchBlock, sdkClientInit);
|
|
53
|
+
// delete ハンドラー: 常に SDK(既存パターン)、PK に応じて分岐
|
|
54
|
+
const deleteHandler = isIdPartition
|
|
55
|
+
? generateDeleteIdPartition(modelCamel, writeGuard, authCatchBlock, sdkClientInit)
|
|
56
|
+
: generateDeleteCustomPartition(modelCamel, partitionKeyField, writeGuard, authCatchBlock, sdkClientInit);
|
|
30
57
|
return `import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
|
|
31
58
|
import { z } from 'zod/v4';
|
|
32
59
|
import crypto from 'crypto';
|
|
@@ -68,38 +95,7 @@ ${authCatchBlock} context.error(\`Error fetching from \${containerName}:\`,
|
|
|
68
95
|
},
|
|
69
96
|
});
|
|
70
97
|
|
|
71
|
-
|
|
72
|
-
app.http('${modelCamel}-get-by-id', {
|
|
73
|
-
methods: ['GET'],
|
|
74
|
-
route: '${modelCamel}/{id}',
|
|
75
|
-
authLevel: 'anonymous',
|
|
76
|
-
extraInputs: [
|
|
77
|
-
{
|
|
78
|
-
type: 'cosmosDB',
|
|
79
|
-
name: 'cosmosInput',
|
|
80
|
-
databaseName: process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase',
|
|
81
|
-
containerName,
|
|
82
|
-
connection: 'CosmosDBConnection',
|
|
83
|
-
id: '{id}',
|
|
84
|
-
partitionKey: '{id}',
|
|
85
|
-
},
|
|
86
|
-
],
|
|
87
|
-
handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
|
|
88
|
-
try {${readGuard}
|
|
89
|
-
const document = context.extraInputs.get('cosmosInput');
|
|
90
|
-
|
|
91
|
-
if (!document) {
|
|
92
|
-
return { status: 404, jsonBody: { error: 'Item not found' } };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const validated = ${schemaName}.parse(document);
|
|
96
|
-
return { status: 200, jsonBody: validated };
|
|
97
|
-
} catch (error) {
|
|
98
|
-
${authCatchBlock} context.error(\`Error fetching item from \${containerName}:\`, error);
|
|
99
|
-
return { status: 500, jsonBody: { error: 'Failed to fetch item' } };
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
});
|
|
98
|
+
${getByIdHandler}
|
|
103
99
|
|
|
104
100
|
// POST /api/${modelCamel} - 新規作成
|
|
105
101
|
app.http('${modelCamel}-create', {
|
|
@@ -145,7 +141,82 @@ ${authCatchBlock} context.error(\`Error creating item in \${containerName}:
|
|
|
145
141
|
},
|
|
146
142
|
});
|
|
147
143
|
|
|
148
|
-
|
|
144
|
+
${updateHandler}
|
|
145
|
+
|
|
146
|
+
${deleteHandler}
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
// --- TS getById ハンドラー生成ヘルパー ---
|
|
150
|
+
function generateGetByIdWithBinding(modelCamel, schemaName, readGuard, authCatchBlock) {
|
|
151
|
+
return `// GET /api/${modelCamel}/{id} - ID指定取得
|
|
152
|
+
app.http('${modelCamel}-get-by-id', {
|
|
153
|
+
methods: ['GET'],
|
|
154
|
+
route: '${modelCamel}/{id}',
|
|
155
|
+
authLevel: 'anonymous',
|
|
156
|
+
extraInputs: [
|
|
157
|
+
{
|
|
158
|
+
type: 'cosmosDB',
|
|
159
|
+
name: 'cosmosInput',
|
|
160
|
+
databaseName: process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase',
|
|
161
|
+
containerName,
|
|
162
|
+
connection: 'CosmosDBConnection',
|
|
163
|
+
id: '{id}',
|
|
164
|
+
partitionKey: '{id}',
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
|
|
168
|
+
try {${readGuard}
|
|
169
|
+
const document = context.extraInputs.get('cosmosInput');
|
|
170
|
+
|
|
171
|
+
if (!document) {
|
|
172
|
+
return { status: 404, jsonBody: { error: 'Item not found' } };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const validated = ${schemaName}.parse(document);
|
|
176
|
+
return { status: 200, jsonBody: validated };
|
|
177
|
+
} catch (error) {
|
|
178
|
+
${authCatchBlock} context.error(\`Error fetching item from \${containerName}:\`, error);
|
|
179
|
+
return { status: 500, jsonBody: { error: 'Failed to fetch item' } };
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
});`;
|
|
183
|
+
}
|
|
184
|
+
function generateGetByIdWithSdk(modelCamel, schemaName, readGuard, authCatchBlock, sdkClientInit) {
|
|
185
|
+
return `// GET /api/${modelCamel}/{id} - ID指定取得 (custom partition key)
|
|
186
|
+
app.http('${modelCamel}-get-by-id', {
|
|
187
|
+
methods: ['GET'],
|
|
188
|
+
route: '${modelCamel}/{id}',
|
|
189
|
+
authLevel: 'anonymous',
|
|
190
|
+
handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
|
|
191
|
+
try {${readGuard}
|
|
192
|
+
const id = request.params.id;
|
|
193
|
+
if (!id) {
|
|
194
|
+
return { status: 400, jsonBody: { error: 'ID is required' } };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
${sdkClientInit}
|
|
198
|
+
|
|
199
|
+
const { resources } = await container.items.query({
|
|
200
|
+
query: 'SELECT * FROM c WHERE c.id = @id',
|
|
201
|
+
parameters: [{ name: '@id', value: id }],
|
|
202
|
+
}).fetchAll();
|
|
203
|
+
|
|
204
|
+
if (!resources || resources.length === 0) {
|
|
205
|
+
return { status: 404, jsonBody: { error: 'Item not found' } };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const validated = ${schemaName}.parse(resources[0]);
|
|
209
|
+
return { status: 200, jsonBody: validated };
|
|
210
|
+
} catch (error) {
|
|
211
|
+
${authCatchBlock} context.error(\`Error fetching item from \${containerName}:\`, error);
|
|
212
|
+
return { status: 500, jsonBody: { error: 'Failed to fetch item' } };
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
});`;
|
|
216
|
+
}
|
|
217
|
+
// --- TS update ハンドラー生成ヘルパー ---
|
|
218
|
+
function generateUpdateWithBinding(modelCamel, schemaName, writeGuard, authCatchBlock) {
|
|
219
|
+
return `// PUT /api/${modelCamel}/{id} - 更新
|
|
149
220
|
app.http('${modelCamel}-update', {
|
|
150
221
|
methods: ['PUT'],
|
|
151
222
|
route: '${modelCamel}/{id}',
|
|
@@ -206,9 +277,63 @@ ${authCatchBlock} context.error(\`Error updating item in \${containerName}:
|
|
|
206
277
|
return { status: 500, jsonBody: { error: 'Failed to update item' } };
|
|
207
278
|
}
|
|
208
279
|
},
|
|
209
|
-
})
|
|
280
|
+
});`;
|
|
281
|
+
}
|
|
282
|
+
function generateUpdateWithSdk(modelCamel, schemaName, partitionKeyField, writeGuard, authCatchBlock, sdkClientInit) {
|
|
283
|
+
return `// PUT /api/${modelCamel}/{id} - 更新 (custom partition key)
|
|
284
|
+
app.http('${modelCamel}-update', {
|
|
285
|
+
methods: ['PUT'],
|
|
286
|
+
route: '${modelCamel}/{id}',
|
|
287
|
+
authLevel: 'anonymous',
|
|
288
|
+
handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
|
|
289
|
+
try {${writeGuard}
|
|
290
|
+
const id = request.params.id;
|
|
291
|
+
if (!id) {
|
|
292
|
+
return { status: 400, jsonBody: { error: 'ID is required' } };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
${sdkClientInit}
|
|
210
296
|
|
|
211
|
-
|
|
297
|
+
const { resources } = await container.items.query({
|
|
298
|
+
query: 'SELECT * FROM c WHERE c.id = @id',
|
|
299
|
+
parameters: [{ name: '@id', value: id }],
|
|
300
|
+
}).fetchAll();
|
|
301
|
+
|
|
302
|
+
if (!resources || resources.length === 0) {
|
|
303
|
+
return { status: 404, jsonBody: { error: 'Item not found' } };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const existingDocument = resources[0];
|
|
307
|
+
const body = await request.json() as any;
|
|
308
|
+
const { createdAt, updatedAt, ...userData } = body;
|
|
309
|
+
|
|
310
|
+
const dataWithManagedFields = {
|
|
311
|
+
...userData,
|
|
312
|
+
id,
|
|
313
|
+
createdAt: existingDocument.createdAt,
|
|
314
|
+
updatedAt: new Date().toISOString(),
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const result = ${schemaName}.safeParse(dataWithManagedFields);
|
|
318
|
+
|
|
319
|
+
if (!result.success) {
|
|
320
|
+
context.error('Validation failed:', result.error.issues);
|
|
321
|
+
return { status: 400, jsonBody: { error: 'Validation failed', details: result.error.issues } };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const pkValue = result.data.${partitionKeyField};
|
|
325
|
+
await container.items.upsert(result.data);
|
|
326
|
+
return { status: 200, jsonBody: result.data };
|
|
327
|
+
} catch (error) {
|
|
328
|
+
${authCatchBlock} context.error(\`Error updating item in \${containerName}:\`, error);
|
|
329
|
+
return { status: 500, jsonBody: { error: 'Failed to update item' } };
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
});`;
|
|
333
|
+
}
|
|
334
|
+
// --- TS delete ハンドラー生成ヘルパー ---
|
|
335
|
+
function generateDeleteIdPartition(modelCamel, writeGuard, authCatchBlock, sdkClientInit) {
|
|
336
|
+
return `// DELETE /api/${modelCamel}/{id} - 削除
|
|
212
337
|
app.http('${modelCamel}-delete', {
|
|
213
338
|
methods: ['DELETE'],
|
|
214
339
|
route: '${modelCamel}/{id}',
|
|
@@ -220,17 +345,7 @@ app.http('${modelCamel}-delete', {
|
|
|
220
345
|
return { status: 400, jsonBody: { error: 'ID is required' } };
|
|
221
346
|
}
|
|
222
347
|
|
|
223
|
-
|
|
224
|
-
const endpoint = process.env.CosmosDBConnection__accountEndpoint;
|
|
225
|
-
let client: InstanceType<typeof CosmosClient>;
|
|
226
|
-
if (endpoint) {
|
|
227
|
-
const { DefaultAzureCredential } = await import('@azure/identity');
|
|
228
|
-
client = new CosmosClient({ endpoint, aadCredentials: new DefaultAzureCredential() });
|
|
229
|
-
} else {
|
|
230
|
-
client = new CosmosClient(process.env.CosmosDBConnection!);
|
|
231
|
-
}
|
|
232
|
-
const database = client.database(process.env.COSMOS_DB_DATABASE_NAME || 'AppDatabase');
|
|
233
|
-
const container = database.container(containerName);
|
|
348
|
+
${sdkClientInit}
|
|
234
349
|
|
|
235
350
|
await container.item(id, id).delete();
|
|
236
351
|
context.log(\`Deleted item \${id} from \${containerName}\`);
|
|
@@ -244,18 +359,119 @@ ${authCatchBlock} context.error(\`Error deleting item from \${containerName
|
|
|
244
359
|
return { status: 500, jsonBody: { error: 'Failed to delete item' } };
|
|
245
360
|
}
|
|
246
361
|
},
|
|
247
|
-
})
|
|
248
|
-
|
|
362
|
+
});`;
|
|
363
|
+
}
|
|
364
|
+
function generateDeleteCustomPartition(modelCamel, partitionKeyField, writeGuard, authCatchBlock, sdkClientInit) {
|
|
365
|
+
return `// DELETE /api/${modelCamel}/{id} - 削除 (custom partition key)
|
|
366
|
+
app.http('${modelCamel}-delete', {
|
|
367
|
+
methods: ['DELETE'],
|
|
368
|
+
route: '${modelCamel}/{id}',
|
|
369
|
+
authLevel: 'anonymous',
|
|
370
|
+
handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
|
|
371
|
+
try {${writeGuard}
|
|
372
|
+
const id = request.params.id;
|
|
373
|
+
if (!id) {
|
|
374
|
+
return { status: 400, jsonBody: { error: 'ID is required' } };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
${sdkClientInit}
|
|
378
|
+
|
|
379
|
+
// パーティションキー値を取得するためにドキュメントを読み取り
|
|
380
|
+
const { resources } = await container.items.query({
|
|
381
|
+
query: 'SELECT * FROM c WHERE c.id = @id',
|
|
382
|
+
parameters: [{ name: '@id', value: id }],
|
|
383
|
+
}).fetchAll();
|
|
384
|
+
|
|
385
|
+
if (!resources || resources.length === 0) {
|
|
386
|
+
return { status: 404, jsonBody: { error: 'Item not found' } };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const pkValue = resources[0].${partitionKeyField};
|
|
390
|
+
await container.item(id, pkValue).delete();
|
|
391
|
+
context.log(\`Deleted item \${id} from \${containerName}\`);
|
|
392
|
+
|
|
393
|
+
return { status: 204 };
|
|
394
|
+
} catch (error: any) {
|
|
395
|
+
if (error.code === 404) {
|
|
396
|
+
return { status: 404, jsonBody: { error: 'Item not found' } };
|
|
397
|
+
}
|
|
398
|
+
${authCatchBlock} context.error(\`Error deleting item from \${containerName}:\`, error);
|
|
399
|
+
return { status: 500, jsonBody: { error: 'Failed to delete item' } };
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
});`;
|
|
249
403
|
}
|
|
250
404
|
function generateCSharpAzureFunctionsCRUD(model, authPolicy) {
|
|
251
405
|
const modelName = model.name;
|
|
252
406
|
const modelCamel = (0, model_parser_1.toCamelCase)(modelName);
|
|
253
407
|
const className = `${modelName}Functions`;
|
|
254
408
|
const containerName = modelName.endsWith('s') ? modelName : `${modelName}s`;
|
|
409
|
+
const partitionKeyPath = model.partitionKey;
|
|
410
|
+
const partitionKeyField = partitionKeyPath.slice(1);
|
|
411
|
+
const isIdPartition = partitionKeyField === 'id';
|
|
255
412
|
const hasAuth = !!authPolicy;
|
|
256
413
|
const authUsing = hasAuth ? 'using Functions.Auth;\n' : '';
|
|
257
414
|
const readGuard = hasAuth ? `\n${(0, auth_generator_1.generateAuthGuardCSharp)(authPolicy, 'read')}\n` : '';
|
|
258
415
|
const writeGuard = hasAuth ? `\n${(0, auth_generator_1.generateAuthGuardCSharp)(authPolicy, 'write')}\n` : '';
|
|
416
|
+
// PK値の取得ロジック(create 用)
|
|
417
|
+
const createPkExpr = isIdPartition
|
|
418
|
+
? 'id'
|
|
419
|
+
: `payload["${partitionKeyField}"]?.GetValue<string>() ?? throw new InvalidOperationException("Partition key field '${partitionKeyField}' is required.")`;
|
|
420
|
+
// ReadCosmosItemAsync: PK=/id の場合は直接読み取り、それ以外はクエリ
|
|
421
|
+
const readItemMethod = isIdPartition
|
|
422
|
+
? ` private static async Task<JsonObject> ReadCosmosItemAsync(Container container, string id)
|
|
423
|
+
{
|
|
424
|
+
var response = await container.ReadItemStreamAsync(id, new PartitionKey(id));
|
|
425
|
+
if (response.StatusCode == HttpStatusCode.NotFound)
|
|
426
|
+
{
|
|
427
|
+
throw new CosmosException("Item not found", HttpStatusCode.NotFound, 0, response.Headers.ActivityId, response.Headers.RequestCharge);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (!response.IsSuccessStatusCode)
|
|
431
|
+
{
|
|
432
|
+
throw new InvalidOperationException($"Cosmos read failed with status {(int)response.StatusCode}.");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
using var document = await JsonDocument.ParseAsync(response.Content);
|
|
436
|
+
return JsonNode.Parse(document.RootElement.GetRawText())?.AsObject()
|
|
437
|
+
?? throw new JsonException("Cosmos item payload must be a JSON object.");
|
|
438
|
+
}`
|
|
439
|
+
: ` private static async Task<JsonObject> ReadCosmosItemAsync(Container container, string id)
|
|
440
|
+
{
|
|
441
|
+
var query = new QueryDefinition("SELECT * FROM c WHERE c.id = @id")
|
|
442
|
+
.WithParameter("@id", id);
|
|
443
|
+
using var iterator = container.GetItemQueryStreamIterator(query);
|
|
444
|
+
|
|
445
|
+
while (iterator.HasMoreResults)
|
|
446
|
+
{
|
|
447
|
+
var page = await iterator.ReadNextAsync();
|
|
448
|
+
if (!page.IsSuccessStatusCode)
|
|
449
|
+
{
|
|
450
|
+
throw new InvalidOperationException($"Cosmos query failed with status {(int)page.StatusCode}.");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
using var document = await JsonDocument.ParseAsync(page.Content);
|
|
454
|
+
if (document.RootElement.TryGetProperty("Documents", out var documents))
|
|
455
|
+
{
|
|
456
|
+
foreach (var item in documents.EnumerateArray())
|
|
457
|
+
{
|
|
458
|
+
return JsonNode.Parse(item.GetRawText())?.AsObject()
|
|
459
|
+
?? throw new JsonException("Cosmos item payload must be a JSON object.");
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
throw new CosmosException("Item not found", HttpStatusCode.NotFound, 0, "", 0);
|
|
465
|
+
}`;
|
|
466
|
+
// Replace / Delete の PK 解決ロジック
|
|
467
|
+
const replacePkExpr = isIdPartition
|
|
468
|
+
? 'new PartitionKey(id)'
|
|
469
|
+
: `new PartitionKey(payload["${partitionKeyField}"]?.GetValue<string>())`;
|
|
470
|
+
const deleteLogic = isIdPartition
|
|
471
|
+
? ` await container.DeleteItemAsync<JsonObject>(id, new PartitionKey(id));`
|
|
472
|
+
: ` var existing = await ReadCosmosItemAsync(container, id);
|
|
473
|
+
var pkValue = existing["${partitionKeyField}"]?.GetValue<string>();
|
|
474
|
+
await container.DeleteItemAsync<JsonObject>(id, new PartitionKey(pkValue));`;
|
|
259
475
|
return `using System.Net;
|
|
260
476
|
using System.Text;
|
|
261
477
|
using System.Text.Json;
|
|
@@ -348,23 +564,7 @@ public sealed class ${className}
|
|
|
348
564
|
private static Stream CreateJsonStream(JsonObject payload) =>
|
|
349
565
|
new MemoryStream(Encoding.UTF8.GetBytes(payload.ToJsonString()));
|
|
350
566
|
|
|
351
|
-
|
|
352
|
-
{
|
|
353
|
-
var response = await container.ReadItemStreamAsync(id, new PartitionKey(id));
|
|
354
|
-
if (response.StatusCode == HttpStatusCode.NotFound)
|
|
355
|
-
{
|
|
356
|
-
throw new CosmosException("Item not found", HttpStatusCode.NotFound, 0, response.Headers.ActivityId, response.Headers.RequestCharge);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
if (!response.IsSuccessStatusCode)
|
|
360
|
-
{
|
|
361
|
-
throw new InvalidOperationException($"Cosmos read failed with status {(int)response.StatusCode}.");
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
using var document = await JsonDocument.ParseAsync(response.Content);
|
|
365
|
-
return JsonNode.Parse(document.RootElement.GetRawText())?.AsObject()
|
|
366
|
-
?? throw new JsonException("Cosmos item payload must be a JSON object.");
|
|
367
|
-
}
|
|
567
|
+
${readItemMethod}
|
|
368
568
|
|
|
369
569
|
private static async Task<HttpResponseData> WriteJsonAsync(HttpRequestData request, HttpStatusCode status, object payload)
|
|
370
570
|
{
|
|
@@ -451,7 +651,8 @@ public sealed class ${className}
|
|
|
451
651
|
using var client = CreateCosmosClient();
|
|
452
652
|
var container = GetContainer(client);
|
|
453
653
|
using var stream = CreateJsonStream(payload);
|
|
454
|
-
var
|
|
654
|
+
var pkValue = ${createPkExpr};
|
|
655
|
+
var response = await container.CreateItemStreamAsync(stream, new PartitionKey(pkValue));
|
|
455
656
|
if (!response.IsSuccessStatusCode)
|
|
456
657
|
{
|
|
457
658
|
throw new InvalidOperationException($"Cosmos create failed with status {(int)response.StatusCode}.");
|
|
@@ -496,7 +697,7 @@ public sealed class ${className}
|
|
|
496
697
|
var payload = BuildManagedDocument(body, id, createdAt, DateTimeOffset.UtcNow.ToString("O"));
|
|
497
698
|
|
|
498
699
|
using var stream = CreateJsonStream(payload);
|
|
499
|
-
var response = await container.ReplaceItemStreamAsync(stream, id,
|
|
700
|
+
var response = await container.ReplaceItemStreamAsync(stream, id, ${replacePkExpr});
|
|
500
701
|
if (!response.IsSuccessStatusCode)
|
|
501
702
|
{
|
|
502
703
|
throw new InvalidOperationException($"Cosmos replace failed with status {(int)response.StatusCode}.");
|
|
@@ -525,7 +726,7 @@ public sealed class ${className}
|
|
|
525
726
|
{${writeGuard}
|
|
526
727
|
using var client = CreateCosmosClient();
|
|
527
728
|
var container = GetContainer(client);
|
|
528
|
-
|
|
729
|
+
${deleteLogic}
|
|
529
730
|
|
|
530
731
|
return request.CreateResponse(HttpStatusCode.NoContent);
|
|
531
732
|
}
|
|
@@ -547,6 +748,9 @@ function generatePythonAzureFunctionsCRUD(model, authPolicy) {
|
|
|
547
748
|
const modelCamel = (0, model_parser_1.toCamelCase)(modelName);
|
|
548
749
|
const modelSnake = (0, model_parser_1.toKebabCase)(modelName).replace(/-/g, "_");
|
|
549
750
|
const containerName = modelName.endsWith('s') ? modelName : `${modelName}s`;
|
|
751
|
+
const partitionKeyPath = model.partitionKey;
|
|
752
|
+
const partitionKeyField = partitionKeyPath.slice(1);
|
|
753
|
+
const isIdPartition = partitionKeyField === 'id';
|
|
550
754
|
const hasAuth = !!authPolicy;
|
|
551
755
|
const authImport = hasAuth ? '\nfrom auth.jwt_helper import require_auth, require_roles, handle_auth_error\n' : '';
|
|
552
756
|
// generateAuthGuardPython outputs at 4-space indent; inside try: we need 8-space
|
|
@@ -555,6 +759,72 @@ function generatePythonAzureFunctionsCRUD(model, authPolicy) {
|
|
|
555
759
|
const readGuard = hasAuth ? '\n' + readGuardRaw.split('\n').map(l => ' ' + l).join('\n') : '';
|
|
556
760
|
const writeGuard = hasAuth ? '\n' + writeGuardRaw.split('\n').map(l => ' ' + l).join('\n') : '';
|
|
557
761
|
const authCatch = hasAuth ? `\n auth_err = handle_auth_error(exc)\n if auth_err:\n return auth_err` : '';
|
|
762
|
+
// getById / update の読み取りロジック
|
|
763
|
+
const getByIdRead = isIdPartition
|
|
764
|
+
? `container.read_item(item=item_id, partition_key=item_id)`
|
|
765
|
+
: `list(container.query_items(
|
|
766
|
+
query="SELECT * FROM c WHERE c.id = @id",
|
|
767
|
+
parameters=[{"name": "@id", "value": item_id}],
|
|
768
|
+
enable_cross_partition_query=True,
|
|
769
|
+
))`;
|
|
770
|
+
const getByIdBody = isIdPartition
|
|
771
|
+
? ` container = _get_container()
|
|
772
|
+
item = container.read_item(item=item_id, partition_key=item_id)
|
|
773
|
+
return _json_response(item, 200)`
|
|
774
|
+
: ` container = _get_container()
|
|
775
|
+
items = list(container.query_items(
|
|
776
|
+
query="SELECT * FROM c WHERE c.id = @id",
|
|
777
|
+
parameters=[{"name": "@id", "value": item_id}],
|
|
778
|
+
enable_cross_partition_query=True,
|
|
779
|
+
))
|
|
780
|
+
if not items:
|
|
781
|
+
return _json_response({"error": "${modelName} not found", "id": item_id}, 404)
|
|
782
|
+
return _json_response(items[0], 200)`;
|
|
783
|
+
const updateBody = isIdPartition
|
|
784
|
+
? ` container = _get_container()
|
|
785
|
+
existing = container.read_item(item=item_id, partition_key=item_id)
|
|
786
|
+
body = req.get_json()
|
|
787
|
+
payload = _build_managed_document(
|
|
788
|
+
body,
|
|
789
|
+
item_id,
|
|
790
|
+
existing.get("createdAt") or datetime.now(timezone.utc).isoformat(),
|
|
791
|
+
datetime.now(timezone.utc).isoformat(),
|
|
792
|
+
)
|
|
793
|
+
container.replace_item(item=item_id, body=payload)
|
|
794
|
+
return _json_response(payload, 200)`
|
|
795
|
+
: ` container = _get_container()
|
|
796
|
+
items = list(container.query_items(
|
|
797
|
+
query="SELECT * FROM c WHERE c.id = @id",
|
|
798
|
+
parameters=[{"name": "@id", "value": item_id}],
|
|
799
|
+
enable_cross_partition_query=True,
|
|
800
|
+
))
|
|
801
|
+
if not items:
|
|
802
|
+
return _json_response({"error": "${modelName} not found", "id": item_id}, 404)
|
|
803
|
+
existing = items[0]
|
|
804
|
+
body = req.get_json()
|
|
805
|
+
payload = _build_managed_document(
|
|
806
|
+
body,
|
|
807
|
+
item_id,
|
|
808
|
+
existing.get("createdAt") or datetime.now(timezone.utc).isoformat(),
|
|
809
|
+
datetime.now(timezone.utc).isoformat(),
|
|
810
|
+
)
|
|
811
|
+
container.replace_item(item=existing, body=payload)
|
|
812
|
+
return _json_response(payload, 200)`;
|
|
813
|
+
const deleteBody = isIdPartition
|
|
814
|
+
? ` container = _get_container()
|
|
815
|
+
container.delete_item(item=item_id, partition_key=item_id)
|
|
816
|
+
return func.HttpResponse(status_code=204)`
|
|
817
|
+
: ` container = _get_container()
|
|
818
|
+
items = list(container.query_items(
|
|
819
|
+
query="SELECT * FROM c WHERE c.id = @id",
|
|
820
|
+
parameters=[{"name": "@id", "value": item_id}],
|
|
821
|
+
enable_cross_partition_query=True,
|
|
822
|
+
))
|
|
823
|
+
if not items:
|
|
824
|
+
return _json_response({"error": "${modelName} not found", "id": item_id}, 404)
|
|
825
|
+
pk_value = items[0].get("${partitionKeyField}")
|
|
826
|
+
container.delete_item(item=item_id, partition_key=pk_value)
|
|
827
|
+
return func.HttpResponse(status_code=204)`;
|
|
558
828
|
return {
|
|
559
829
|
registration: `from blueprints.${modelSnake} import bp as ${modelSnake}_bp\napp.register_blueprint(${modelSnake}_bp)`,
|
|
560
830
|
blueprint: `import json
|
|
@@ -625,9 +895,7 @@ def ${modelSnake}_get_all(req: func.HttpRequest) -> func.HttpResponse:
|
|
|
625
895
|
def ${modelSnake}_get_by_id(req: func.HttpRequest) -> func.HttpResponse:
|
|
626
896
|
item_id = req.route_params.get("id")
|
|
627
897
|
try:${readGuard}
|
|
628
|
-
|
|
629
|
-
item = container.read_item(item=item_id, partition_key=item_id)
|
|
630
|
-
return _json_response(item, 200)
|
|
898
|
+
${getByIdBody}
|
|
631
899
|
except exceptions.CosmosResourceNotFoundError:
|
|
632
900
|
return _json_response({"error": "${modelName} not found", "id": item_id}, 404)
|
|
633
901
|
except Exception as exc:${authCatch}
|
|
@@ -655,17 +923,7 @@ def ${modelSnake}_create(req: func.HttpRequest) -> func.HttpResponse:
|
|
|
655
923
|
def ${modelSnake}_update(req: func.HttpRequest) -> func.HttpResponse:
|
|
656
924
|
item_id = req.route_params.get("id")
|
|
657
925
|
try:${writeGuard}
|
|
658
|
-
|
|
659
|
-
existing = container.read_item(item=item_id, partition_key=item_id)
|
|
660
|
-
body = req.get_json()
|
|
661
|
-
payload = _build_managed_document(
|
|
662
|
-
body,
|
|
663
|
-
item_id,
|
|
664
|
-
existing.get("createdAt") or datetime.now(timezone.utc).isoformat(),
|
|
665
|
-
datetime.now(timezone.utc).isoformat(),
|
|
666
|
-
)
|
|
667
|
-
container.replace_item(item=item_id, body=payload)
|
|
668
|
-
return _json_response(payload, 200)
|
|
926
|
+
${updateBody}
|
|
669
927
|
except exceptions.CosmosResourceNotFoundError:
|
|
670
928
|
return _json_response({"error": "${modelName} not found", "id": item_id}, 404)
|
|
671
929
|
except ValueError:
|
|
@@ -678,9 +936,7 @@ def ${modelSnake}_update(req: func.HttpRequest) -> func.HttpResponse:
|
|
|
678
936
|
def ${modelSnake}_delete(req: func.HttpRequest) -> func.HttpResponse:
|
|
679
937
|
item_id = req.route_params.get("id")
|
|
680
938
|
try:${writeGuard}
|
|
681
|
-
|
|
682
|
-
container.delete_item(item=item_id, partition_key=item_id)
|
|
683
|
-
return func.HttpResponse(status_code=204)
|
|
939
|
+
${deleteBody}
|
|
684
940
|
except exceptions.CosmosResourceNotFoundError:
|
|
685
941
|
return _json_response({"error": "${modelName} not found", "id": item_id}, 404)
|
|
686
942
|
except Exception as exc:${authCatch}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"functions-generator.js","sourceRoot":"","sources":["../../../src/core/scaffold/functions-generator.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;AAUH,
|
|
1
|
+
{"version":3,"file":"functions-generator.js","sourceRoot":"","sources":["../../../src/core/scaffold/functions-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@ export interface ModelInfo {
|
|
|
14
14
|
nestedSchemaRefs: NestedSchemaRef[];
|
|
15
15
|
connectorConfig?: ModelConnectorConfig;
|
|
16
16
|
authPolicy?: ModelAuthPolicy;
|
|
17
|
+
partitionKey: string;
|
|
17
18
|
}
|
|
18
19
|
export interface FieldInfo {
|
|
19
20
|
name: string;
|
|
@@ -61,6 +62,12 @@ export declare function parseConnectorConfig(content: string): ModelConnectorCon
|
|
|
61
62
|
* パターン: export const authPolicy = { roles: [...], read: [...], write: [...] }
|
|
62
63
|
*/
|
|
63
64
|
export declare function parseAuthPolicy(content: string): ModelAuthPolicy | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* パーティションキーを抽出する
|
|
67
|
+
* export const partitionKey = '/tenantId' のようなエクスポートを検出
|
|
68
|
+
* 未指定の場合はデフォルト '/id' を返す
|
|
69
|
+
*/
|
|
70
|
+
export declare function parsePartitionKey(content: string): string;
|
|
64
71
|
/**
|
|
65
72
|
* 文字列を kebab-case に変換
|
|
66
73
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-parser.d.ts","sourceRoot":"","sources":["../../../src/core/scaffold/model-parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEpE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,eAAe,CAAC,EAAE,oBAAoB,CAAC;IACvC,UAAU,CAAC,EAAE,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"model-parser.d.ts","sourceRoot":"","sources":["../../../src/core/scaffold/model-parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEpE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,OAAO,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,eAAe,CAAC,EAAE,oBAAoB,CAAC;IACvC,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAgF1E;AAufD;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CA2FtF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAsC5E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,SAAS,GAAE,MAAwB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAwB5F"}
|
|
@@ -41,6 +41,7 @@ exports.toPascalCase = toPascalCase;
|
|
|
41
41
|
exports.toCamelCase = toCamelCase;
|
|
42
42
|
exports.parseConnectorConfig = parseConnectorConfig;
|
|
43
43
|
exports.parseAuthPolicy = parseAuthPolicy;
|
|
44
|
+
exports.parsePartitionKey = parsePartitionKey;
|
|
44
45
|
exports.toKebabCase = toKebabCase;
|
|
45
46
|
exports.getAllModels = getAllModels;
|
|
46
47
|
const fs = __importStar(require("fs"));
|
|
@@ -78,6 +79,8 @@ async function parseModelFile(modelPath) {
|
|
|
78
79
|
const connectorConfig = parseConnectorConfig(content);
|
|
79
80
|
// authPolicy を抽出(ロールベースアクセス制御用メタデータ)
|
|
80
81
|
const authPolicy = parseAuthPolicy(content);
|
|
82
|
+
// partitionKey を抽出(Cosmos DB パーティションキー)
|
|
83
|
+
const partitionKey = parsePartitionKey(content);
|
|
81
84
|
// ネストしたスキーマ参照を検出
|
|
82
85
|
const nestedSchemaRefs = detectNestedSchemaRefs(modelPath, content, schemaName);
|
|
83
86
|
// フィールド情報を抽出(動的インポートを使用)
|
|
@@ -88,6 +91,16 @@ async function parseModelFile(modelPath) {
|
|
|
88
91
|
const hasId = fields.some(f => f.name === "id");
|
|
89
92
|
const hasCreatedAt = fields.some(f => f.name === "createdAt");
|
|
90
93
|
const hasUpdatedAt = fields.some(f => f.name === "updatedAt");
|
|
94
|
+
// パーティションキーのバリデーション
|
|
95
|
+
if (partitionKey !== '/id') {
|
|
96
|
+
if (!partitionKey.startsWith('/')) {
|
|
97
|
+
console.warn(`⚠️ [${modelName}] partitionKey should start with '/': got '${partitionKey}'`);
|
|
98
|
+
}
|
|
99
|
+
const pkField = partitionKey.startsWith('/') ? partitionKey.slice(1) : partitionKey;
|
|
100
|
+
if (fields.length > 0 && !fields.some(f => f.name === pkField)) {
|
|
101
|
+
console.warn(`⚠️ [${modelName}] partitionKey field '${pkField}' not found in schema fields`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
91
104
|
return {
|
|
92
105
|
name: modelName,
|
|
93
106
|
displayName,
|
|
@@ -98,6 +111,7 @@ async function parseModelFile(modelPath) {
|
|
|
98
111
|
hasCreatedAt,
|
|
99
112
|
hasUpdatedAt,
|
|
100
113
|
nestedSchemaRefs,
|
|
114
|
+
partitionKey,
|
|
101
115
|
...(connectorConfig ? { connectorConfig } : {}),
|
|
102
116
|
...(authPolicy ? { authPolicy } : {}),
|
|
103
117
|
};
|
|
@@ -693,6 +707,15 @@ function parseAuthPolicy(content) {
|
|
|
693
707
|
...(write ? { write } : {}),
|
|
694
708
|
};
|
|
695
709
|
}
|
|
710
|
+
/**
|
|
711
|
+
* パーティションキーを抽出する
|
|
712
|
+
* export const partitionKey = '/tenantId' のようなエクスポートを検出
|
|
713
|
+
* 未指定の場合はデフォルト '/id' を返す
|
|
714
|
+
*/
|
|
715
|
+
function parsePartitionKey(content) {
|
|
716
|
+
const match = content.match(/export\s+const\s+partitionKey\s*=\s*['"]([^'"]+)['"]/);
|
|
717
|
+
return match ? match[1] : '/id';
|
|
718
|
+
}
|
|
696
719
|
/**
|
|
697
720
|
* 文字列を kebab-case に変換
|
|
698
721
|
*/
|